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?
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 publicaccess
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 templatedaccess
(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 ofTester
and renamed it (to something likeaccess2
), then we are allowed to separate the declaration and the definition of the publicaccess
function (Demo)