Невозможно создать экземпляр шаблона функции, который использует decltype для вывода типа возвращаемого значения, если он вызывается из лямбды?

Я пытаюсь использовать С++ 0x и, в частности, лямбда-выражение и decltype, чтобы упростить часть моего кода, используя компилятор MSVC10 RC.

Я столкнулся со следующей очень странной проблемой:

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)'
  });
}

Как указано в комментарии, компилятор выдает ошибку на строке foo([]() { }).

Я ненавижу кричать "ошибка компилятора", но я действительно не вижу никакого хорошего объяснения этой ошибке. Судя по всему, находясь внутри внешнего лямбда-выражения, компилятор не может специализировать шаблон функции foo для внутреннего лямбда-выражения.

Однако, если определение foo изменено, чтобы жестко указать тип возвращаемого значения, например:

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

тогда все компилируется нормально.

Есть ли какая-то непонятная особенность decltype при использовании для вывода возвращаемого типа параметров лямбда-выражения внутри области действия другой лямбда-выражения, о которой я не знаю?


person jalf    schedule 25.02.2010    source источник
comment
Поскольку у меня нет доступа к этому компилятору: помогает ли изменение конечной части возвращаемого типа на -> decltype(void(), f())?   -  person Johannes Schaub - litb    schedule 25.02.2010
comment
@Johannes: Нет, все тот же результат   -  person jalf    schedule 25.02.2010
comment
Что произойдет, если вы удалите bar и просто попытаетесь создать экземпляр foo с пустой лямбдой? foo( [](){} )   -  person David Rodríguez - dribeas    schedule 25.02.2010
comment
@David: Тогда все работает нормально. Это самый маленький образец, который я смог придумать, который все еще воспроизводит проблему.   -  person jalf    schedule 25.02.2010
comment
Я не удивлюсь, если это будет связано с: stackoverflow.com/questions/2122282/ Кажется, что лямбда-выражение и область видимости не очень хорошо сочетаются в VS2010. Кстати, мои баги до сих пор существуют в RC.   -  person GManNickG    schedule 25.02.2010
comment
Также, если вы хотите упростить проблему, вы можете избавиться от bar: int main() { auto x = []() { foo([](){}); }; } Выдает ту же ошибку.   -  person GManNickG    schedule 25.02.2010


Ответы (2)


Это всего лишь несколько тестовых примеров для людей.

Работает

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);
            };
}

Не удается

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; }); 
            };
}

Так что, похоже, это связано с областью действия и типом void внутренней лямбда-выражения, даже если оно указано явно.(?)

person Community    schedule 25.02.2010
comment
Интересно. Похоже, вы столкнулись с очень похожими проблемами. Определенно похоже на ошибку компилятора - person jalf; 26.02.2010
comment
@jalf: Да, надеюсь, они тоже это исправят. - person GManNickG; 26.02.2010
comment
Я добавил рабочий пример: проблемы возникают только тогда, когда возвращаемый тип аргумента для foo равен void - person David Rodríguez - dribeas; 26.02.2010
comment
@ Дэвид, интересно, сработает ли это, если мы изменим внутреннюю лямбду на []() -> void { } или []() { return void(); } ? - person Johannes Schaub - litb; 26.02.2010
comment
Я тестировал с []()->void {}, и он не скомпилировался. У меня нет под рукой VS2010, поэтому я не могу протестировать [](){ return void(); } (можете ли вы сделать это в операторе return?) - person David Rodríguez - dribeas; 26.02.2010
comment
@David, хотя это незаконно в C, это разрешено в C++. Это удобно в универсальном программировании, когда вы можете не знать тип возвращаемого значения (подумайте о boost::function<void()>). Подойдет любое пустое выражение. Например, return throw 4; (выражение throw имеет тип void) или return delete new int;, или return void();, или return function_returning_void();. - person Johannes Schaub - litb; 26.02.2010
comment
@David: я пытаюсь скомпилировать ваш целочисленный код, но и с этим получаю ту же ошибку. Вы уверены, что это работает? Я нахожусь в RC, и я попробовал приведенный выше код (который также не удался как более подробный вариант bar) и получил ту же ошибку, что и варианты void. Так что я переместил int на провал, но извиняюсь, если я ошибаюсь. :) - person GManNickG; 26.02.2010
comment
@litb, @David: Кроме того, я протестировал оба варианта явного возврата пустоты, оба терпят неудачу. То же самое с этой версией int, я не могу работать :X - person GManNickG; 26.02.2010

Природа «авто» позволяет компилятору вычислить тип. Но ваш первый пример содержит рекурсивные ссылки друг на друга, поэтому для вычисления auto из foo вам нужен бар, а для создания экземпляра бара вам нужен foo.

С другой стороны, второй пример явно сообщает компилятору: «Это должен быть указатель на функцию, так что успокойтесь на время». Поскольку указатель на функцию хорошо вычисляется, компилятор знает, что именно будет зарезервировано. Просто для аналога: сравните предварительное объявление члена

struct A; //forward
...
A a1; //this is an error
A *a2; //this is correct since pointer calculated in bytes
person Dewfy    schedule 25.02.2010
comment
Хм, насколько я понимаю, foo никоим образом не зависит от bar. foo - это просто фиктивная функция, которая оценивает функтор, переданный ей в качестве параметра, и возвращает результат этого. И он вызывается с маленькой неработающей лямбдой в качестве аргумента. Ничего общего с bar вообще. Я пропустил что-то действительно очевидное здесь? - person jalf; 25.02.2010
comment
@jalf - указывая функцию шаблона и используя ее неявно, вы заставляете компилятор вычислять тип. Просто попробуйте изменить неявное на явное: foo‹Some-explicit-Type›([]() { }); - person Dewfy; 25.02.2010
comment
Но аргумент шаблона для foo — это просто безымянная лямбда (которую я не могу указать явно). Он должен иметь возможность вывести этот тип, вообще не обращаясь к bar. - person jalf; 25.02.2010
comment
@jalf - ДА! Я именно об этом. безымянная лямбда из вашего примера создает рекурсивную ссылку - компилятор не может ее вывести. Так что сломайте его, но какое-то явное объявление - person Dewfy; 25.02.2010
comment
Рекурсивная ссылка на что? - person jalf; 25.02.2010
comment
(1) bar принимает безымянную лямбду, пытаясь вывести ее тип путем разрешения аргумента (2) foo использует шаблонный аргумент F, который является неявным, поэтому давайте попробуем вывести из bar, (1) .... - person Dewfy; 25.02.2010
comment
foo использует аргумент шаблона F, который является неявным, но почему он должен быть выведен из bar? Я все еще этого не вижу. Помните, что это два разных лямбда-выражения. Лямбда, переданная в bar, не имеет ничего общего с лямбдой, переданной в foo. - person jalf; 25.02.2010
comment
foo использует аргумент шаблона F, который является неявным, но почему он должен быть выведен из bar - но как компилятор может вывести эту информацию? так что у вас есть ошибка выше. Я не говорю, что компилятор должен выводить тип из строки. Но в вашем примере больше нет дополнительной информации для компилятора, поэтому он просто попробует вывести. - person Dewfy; 25.02.2010
comment
@Dewfy, я тоже не понимаю твоей точки зрения. Чем это отличается от int a(int); template<typename T> T g(T); template<typename T> void f(T); int main() { f(a(g(a(0)))); } ? - person Johannes Schaub - litb; 25.02.2010