Trailing return types and tag dispatching

217 Views Asked by At

Experimenting with trailing return types and tag dispatching, I have written the following code.

#include <string>
#include <iostream>

using namespace std;

namespace Params
{
struct t_param1{};
struct t_param2{};
};

template<typename t_detail>
struct Select;

template<>
struct Select<Params::t_param1> {using choice = Params::t_param1;};

template<>
struct Select<Params::t_param2> {using choice = Params::t_param2;};

class Tester
{
private:
    using t_uint32 = uint32_t;
    using t_string = string;

private:
    t_uint32 m_param1;
//      t_string m_param2;

private:
    template<typename t_entity>
    void assign(const Params::t_param1&, t_entity&& entity);

    template<typename t_entity>
    void assign(const Params::t_param2&, t_entity&& entity);

    auto access(const Params::t_param1&) -> decltype(m_param1);
//      auto access(const Params::t_param2&) -> decltype(m_param2);


public:
    template<typename t_detail, typename t_entity>
    void assign(t_entity&& entity);

    template<typename t_detail>
    auto access() -> decltype(access(typename Select<t_detail>::choice()));
};

template<typename t_detail, typename t_entity>
void
Tester::assign(t_entity&& entity)
{
assign(typename Select<t_detail>::choice(), entity);
}


template<typename t_entity>
void
Tester::assign(const Params::t_param1&, t_entity&& entity)
{
m_param1 = entity;
cout << "Assigned m_param1 with " << entity << endl;
}

/*
template<typename t_entity>
void
Tester::assign(const Params::t_param2&, t_entity&& entity)
{
m_param2 = entity;
cout << "Assigned m_param2 with " << entity << endl;
}
*/



template<typename t_detail>
auto
Tester::access()
-> decltype(access(typename Select<t_detail>::choice()))
{
return(access(typename Select<t_detail>::choice()));
}

auto
Tester::access(const Params::t_param1&)
-> decltype(m_param1)
{
return(m_param1);
}

/*
auto
Tester::access(const Params::t_param2&)
-> decltype(m_param2)
{
return(m_param2);
}
*/

int main() {
auto tester = Tester();
tester.assign<Params::t_param1>(79);
//  tester.assign<Params::t_param2>("viziv");

auto param1 = tester.access<Params::t_param1>();
//  auto param2 = tester.access<Params::t_param2>();

cout << "Access: param1 = " << param1 << endl;
//  cout << "Access: param2 = " << param2 << endl;

return 0;
}

when I compile this code using Apple LLVM version 7.0.2 (clang-700.1.81), I get the following compilation error

junk1.cpp:78:9: error: out-of-line definition of 'access' does not match any declaration in 'Tester'
Tester::access()
        ^~~~~~
1 error generated.

Curiously, when I uncomment the code to assign and access param2 (commented out in the above code), it compiles fine and produces the required result.

What am I doing wrong? Could anyone please explain to me why the inclusion of param2 change in compilation behaviour?

1

There are 1 best solutions below

2
On

I think there is one and a half issues going on here.

The first lies in that using a trailing return type essentially creates a templated function. When you attempt to use a class' function, the class type must not be incomplete.

That is why moving the function definition for the public access method into the class declaration fixes it (Demo); the class is otherwise incomplete so long as the public access method hasn't been defined, and that method cannot be defined until the class is complete.

Note that another way to fix this would be if the private version of access were somehow a non-member function (e.g., a free-floating function in the surrounding scope).

The problem with that approach (half of a problem, because you're not actually trying to do this) is that, trying to call the now-free floating version of access requires the compiler to evaluate all possible overloads, including the public templated access (Thanks to ADL). When that happens, Select<t_detail>::choice is evaluated in a non-deduced context, and the actual underlying type cannot be obtained.

So, if we both moved the private access outside of Tester and renamed it (to something like access2), then we are allowed to separate the declaration and the definition of the public access function (Demo)