negamartin negamartin - 1 month ago 7
C++ Question

C++ Type-only template argument to lambda

Imagine I've got this struct:

struct Foo {
operator int() {
return 11;
}
operator unsigned int() {
return 22;
}
} foo;


When this struct is casted to an int, it returns 11, but when casted to an unsigned int, it returns 22.

Using normal functions, I could use templates and a getter function to choose:

template<typename T>
T get() {
return (T)foo;
}


Now, when calling this function like
get<int>()
it would return
11
, but when calling it like
get<unsigned int>()
it would return
22
.

Everything's alright until now, when I try to use lambdas instead:

auto lambda=[](auto type) {
return (decltype(type))foo;
};


Now when calling lambda as
lambda(0)
it returns
11
, and calling it as
lambda(0U)
returns
22
.

This works correctly, although rather 'hacky', but an instance of the type needs to be used, which wouldn't be ideal with larger types.
So another way springs up, even 'hackier', to achieve this:

auto lambda=[](auto* typePointer) {
return (decltype(*typePointer))foo;
};


Now calling it as
lambda((int*)NULL)
returns
11
but calling it as
lambda((unsigned int*)NULL)
returns
22
.
As you might have noticed this is rather verbose and 'hacky', so I tried a more straightforward and traditional method:

auto lambda=[]<typename T>() {
return (T)foo;
};


At first I thought it wouldn't compile, since I haven't seen this syntax anywhere, but it does compile (at least with GCC). However, when trying to call it, errors show up:

lambda();


testlambda.cpp: In function ‘int main()’:
testlambda.cpp:25:9: error: no match for call to ‘(main()::<lambda()>) ()’
lambda();
^
testlambda.cpp:22:29: note: candidate: template<class T> main()::<lambda()>
auto lambda=[]<typename T>() {
^
testlambda.cpp:22:29: note: template argument deduction/substitution failed:
testlambda.cpp:25:9: note: couldn't deduce template parameter ‘T’
lambda();
^


As you can see, a candidate is
template<class T> main()::<lambda()>
, but this doesn't compile either:

lambda<int>()
->
error: expected primary-expression before ‘int’


So, my question is: What is the official, standard-compliant way of doing this, if any? I really hope the pointer hack isn't the only way. It seems really clumsy to use in real code.

I'm using G++ (GCC 5.4.0) as my compiler. I'm also using the C++14 standard like
-std=c++14
.

Answer

You can pass a variable template of an empty tag type:

template <class T> struct tag_t { using type = T; };
template <class T>
constexpr tag_t<T> tag{};

You can write your lambda like:

auto lambda = [](auto type) {
    return static_cast<typename decltype(type)::type>(foo);
};

or

auto lambda = [](auto type) -> typename decltype(type)::type {
    return foo;
};

and call it like:

lambda(tag<int> ); // 11
lambda(tag<unsigned> ); // 22
Comments