Как получить возвращаемый тип функции-члена из класса?

следующая программа выдает ошибку компиляции с clang, хотя он переходит на другие компиляторы:

#include <utility>

struct foo
{
  auto bar() -> decltype(0)
  {
    return 0;
  }

  using bar_type = decltype(std::declval<foo>().bar());
};

int main()
{
  return 0;
}

clang выходы:

$ clang -std=c++11 clang_repro.cpp 
clang_repro.cpp:10:48: error: member access into incomplete type 'foo'
  using bar_type = decltype(std::declval<foo>().bar());
                                               ^
clang_repro.cpp:3:8: note: definition of 'foo' is not complete until the closing '}'
struct foo
       ^
1 error generated.

является ли эта программа незаконной, и если да, есть ли правильный способ определить foo::bar_type?

clang детали:

$ clang --version
Ubuntu clang version 3.5-1ubuntu1 (trunk) (based on LLVM 3.5)
Target: x86_64-pc-linux-gnu
Thread model: posix

2 ответов


g++4.9 выдает ту же ошибку

я не уверен, что это недопустимый код, потому что неполные типы разрешены для declval, и выражение decltype не оценивается.
rightføld в своем ответе объяснил очень хорошо, почему этот код недействителен.

можно использовать std:: result_of:

using bar_type = std::result_of<decltype(&foo::bar)(foo)>::type;

который фактически реализован следующим образом:

using bar_type = decltype((std::declval<foo>().*std::declval<decltype(&foo::bar)>())());

разница между этим и кодом в вопросе находится оператор указателя на член (.*) используется вместо оператора доступа к члену (.), и он не требует, чтобы тип был полным, что демонстрируется этим кодом:

#include <utility>
struct foo;
int main() {
    int (foo::*pbar)();
    using bar_type = decltype((std::declval<foo>().*pbar)());
}

§7.1.6.2 говорит:

выражение e тип обозначается decltype(e) определяется следующим образом:

  • если e является непарентированным id-выражением или непарентированным доступом члена класса (5.2.5),decltype(e) - тип сущности с именем e. ...
  • ...

§5.2.5 говорит:

для первого варианта (точка) первое выражение имеет полный тип класса. ...

§9.2 говорит:

класс считается полностью определенным типом объекта (3.9) (или полным типом) при закрытии } спецификатора класса. ...

decltype(std::declval<foo>().bar()) (и в свою очередь std::declval<foo>().bar()) появляется перед закрывающим тегом }, так что foo является неполным, так std::declval<foo>().bar() плохо сформирован, поэтому clang является правильным.