Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 360
negamartin Avatar asked Oct 09 '16 19:10

negamartin


Video Answer


1 Answers

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
like image 88
Barry Avatar answered Sep 21 '22 08:09

Barry