Unable to instantiate function templates which uses decltype to deduce return type, if called from inside a lambda?
Asked Answered
K

2

9

I'm trying to use C++0x, and in particular lambda expression and decltype to simplify some of my code, using the MSVC10 RC compiler.

I've run into the following very odd problem:

template <typename F>
auto foo(F f) -> decltype(f()){
  return f();
}

template <typename F>
void bar(F f){
  f();
}

int main() {
  bar([](){
    foo([]() { }); // error C2893: Failed to specialize function template ''unknown-type' foo(F)'
  });
}

As indicated in the comment, the compiler generates an error on the line foo([]() { }).

I hate to shout "compiler bug", but I really can't see any good explanation for this error. Apparently, while inside the outer lambda expression, the compiler can not specialize the foo function template for the inner lambda.

However, if the definition of foo is changed to hardcode the return type, like this:

template <typename F>
void foo(F f){
  return f();
}

then everything compiles just fine.

Is there some obscure quirk of decltype when used to deduce the return type of lambda expression parameters inside the scope of another lambda that I'm not aware of?

Krol answered 25/2, 2010 at 16:13 Comment(6)
Since i've no access to that compiler: Does changing the trailing return type part to -> decltype(void(), f()) help?Curse
@Johannes: Nope, still the same resultKrol
What happens if you remove the bar and just try to instantiate foo with an empty lambda? foo( [](){} )Margret
@David: Then it works fine. This is pretty much the smallest sample I could come up with that still reproduces the problem.Krol
I wouldn't be surprised if it were related to: #2122782 It seems lambda's and scope don't play well together in VS2010. My bugs still exist in RC, by the way.Rodrique
Also if you want to simplify the problem, you can get rid of bar: int main() { auto x = []() { foo([](){}); }; } Produces the same error.Rodrique
R
3

These are just some test cases for people to observe.

Works

template <typename F>
auto foo(F f) -> decltype(f())
{
  return f();
}

void dummy() {}

int main()
{
    auto x = []()
            {   // non-lambda parameter
                foo(dummy);
            };
}

template <typename F>
auto foo(F f) -> decltype(f())
{
  return f();
}

int main()
{
    auto f = [](){};
    auto x = [&]()
            {    // pre-defined lambda
                foo(f);
            };
}

Fails

template <typename F>
auto foo(F f) -> decltype(f())
{
  return f();
}

int main()
{
    auto x = []()
            {   // in-argument lambda
                foo([]{});
            };
}

template <typename F>
auto foo(F f) -> decltype(f())
{
  return f();
}

int main()
{
    auto x = []()
            {   // in-scope lambda
                auto f = []{};
                foo(f);
            };
}

template <typename F>
auto foo(F f) -> decltype(f())
{
  return f();
}

int main()
{
    auto x = []()
            {   // in-scope lambda, explicit return
                // (explicit return type fails too, `-> void`)
                auto f = [](){ return; };
                foo(f);
            };
}

template <typename F>
auto foo(F f) -> decltype(f())
{
  return f();
}

int main()
{
    auto x = []()
            {   // in-argument lambda, explicit return non-void
                // (explicit return type fails too, `-> int`)
                foo([]{ return 5; }); 
            };
}

So it does seem to have to do with scope and the void type of the internal lambda, even when made explicit.(?)

Rodrique answered 25/2, 2010 at 16:13 Comment(8)
Interesting. Looks like you've run into some very similar issues. Definitely looks like a compiler bugKrol
@jalf: Ya, hopefully they fix it too.Rodrique
I have added a working example: the problems seems to arise only when the return type of the argument to foo is voidMargret
@David, I wonder whether it works if we change the inner lambda to []() -> void { } or []() { return void(); } ?Curse
I tested with []()->void {} and it did not compile. I don't have VS2010 at hand, so I cannot test [](){ return void(); } (can you actually do that in a return statement?)Margret
@David while it's illegal in C, it's legal in C++. It comes handy in generic programming where you may not know the return type (think of boost::function<void()>). Any void expression will do. For instance return throw 4; (a throw expression has type void) or return delete new int; or return void(); or return function_returning_void();Curse
@David: I'm trying to get your integer code to compile, but I get the same error with that as well. Are you sure it works? I'm in RC, and I tried the code above (which failed as the more verbose bar variant as well), and get the same error as the void variants. So I'm moved the int to fail, but apologies if I'm mistaken. :)Rodrique
@litb, @David: Also, I tested both variants of the explicit void return, both fail. Same with that int version I can't get working :XRodrique
T
0

The nature of 'auto' is allow compiler to calculate the type. But your first example is contains recursive references on each other, so to calculate auto's of foo you need the bar and to create instance of bar you need the foo.

On other hand second example explicitly tells compiler: "It should be a pointer to function, so calm down for a time". Since pointer to function is well calculated type compiler knows what exactly will be reserved. Just for analogue: compare member's forward declaration

struct A; //forward
...
A a1; //this is an error
A *a2; //this is correct since pointer calculated in bytes
Ted answered 25/2, 2010 at 16:43 Comment(9)
Hmm, foo doesn't in any way depend on bar as far as I can figure out. foo is just a dummy function which evaluates the functor it's passed as its parameter, and returns the result of that. And it is called with a small no-op lambda as its argument. Nothing to do with bar at all. Am I missing something really obvious here?Krol
@jalf - specifying template function and using it implicitly you enforces compiler to calculate type. Just try to change implicit to explicit: foo<Some-explicit-Type>([]() { });Ted
But the template argument for foo is just the unnamed lambda (which I can't very well specify explicitly). It should be able to deduce that type without referring to bar at allKrol
@jalf - YES! I'm exactly talking about it. unnamed lambda from your example creates recursive reference - compiler cannot deduce it. So break it but some explicit declarationTed
A recursive reference to what?Krol
(1) bar accepts unnamed lambda, trying deduce it type by resolving argument (2) foo uses template argument F that is implicit, so let's try to be deduced from bar, (1) ....Ted
foo uses template argument F which is implicit, but why should it be deduced from bar? I'm still not seeing it. Remember they're two different lambda expressions. The lambda passed to bar has nothing to do with the lambda passed to foo.Krol
"foo uses template argument F which is implicit, but why should it be deduced from bar" - but how complier can deduce this info? so you have got an error above. I don't talk that compiler must deduce type from bar. But in your example no more additional info for compiler, that is why it just try to deduce.Ted
@Dewfy, i don't see your point either. How is this different from int a(int); template<typename T> T g(T); template<typename T> void f(T); int main() { f(a(g(a(0)))); } ?Curse

© 2022 - 2024 — McMap. All rights reserved.