views:

309

answers:

2

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?

A: 

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
Dewfy
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?
jalf
@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>([]() { });
Dewfy
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 all
jalf
@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 declaration
Dewfy
A recursive reference to what?
jalf
(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) ....
Dewfy
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.
jalf
"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.
Dewfy
@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)))); }` ?
Johannes Schaub - litb
+2  A: 

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-declared 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 void(); };
                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.(?)

GMan
Interesting. Looks like you've run into some very similar issues. Definitely looks like a compiler bug
jalf
@jalf: Ya, hopefully they fix it too.
GMan
I have added a working example: the problems seems to arise only when the return type of the argument to `foo` is `void`
David Rodríguez - dribeas
@David, I wonder whether it works if we change the inner lambda to `[]() -> void { }` or `[]() { return void(); }` ?
Johannes Schaub - litb
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?)
David Rodríguez - dribeas
@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();`
Johannes Schaub - litb
@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. :)
GMan
@litb, @David: Also, I tested both variants of the explicit void return, both fail. Same with that `int` version I can't get working :X
GMan