В чем разница между decltype (auto) и decltype (return expr) как возвращаемый тип?

В чем разница между decltype(auto) и decltype(returning expression) как возвращаемым типом функции (шаблона), если expr используется без круглых скобок в обоих случаях?

auto f() -> decltype(auto) { return expr; } // 1
auto f() -> decltype(expr) { return expr; } // 2

Выше f может быть определено/объявлено в любом контексте и может быть либо (членной) функцией, либо шаблоном функции (члена), либо даже (общей) лямбдой. expr может зависеть от любых параметров шаблона.

Во второй версии оба expr являются точно таким же выражением без дополнительных круглых скобок.

Какие различия можно ожидать, используя первую или вторую форму в С++ 14 и более поздних версиях?

Что делать, если круглые скобки используются повсюду?

Ответ 1

Да, есть разница. Первый будет определять тип возврата, основанный на возвратном выражении в теле функции.

Второй не будет также устанавливать тип возврата в тип выражения внутри decltype(), но также будет применять выражение sfinae на нем. Это означает, что если выражение внутри decltype недопустимо, компилятор будет искать другую действительную перегрузку. В то время как первая версия будет жесткой ошибкой.

Возьмем этот пример:

template<typename T>
auto fun(T a) -> decltype(a.f()) { return a.f(); }

template<typename T>
auto fun(T a) -> decltype(a.g()) { return a.g(); }

struct SomeType {
    int g() { return 0; }
};

fun(SomeType{});

Выберите правильную перегрузку. Теперь, если мы заменим decltype(expr) на decltype(auto), компилятор не сможет выбрать правильную перегрузку, так как в сигнатуре функции нет ничего, что ограничивало бы то, что тип должен быть способен.

Ответ 2

decltype(auto) используется для

  • Введите возвращаемый тип в общий код, вы не хотите вводить много дубликатов.

    template<class Func, class... Args>
    decltype(auto) foo(Func f, Args&&... args) 
    { 
        return f(std::forward<Args>(args)...); 
    }
    
  • Вывод типа задержки, как вы можете видеть в этом вопросе, у компиляторов есть некоторая проблема с out decltype(auto)

    • Это не работает, как вы можете видеть с помощью g++ и clang++

      template<int i> struct Int {};
      constexpr auto iter(Int<0>) -> Int<0>;
      
      template<int i> constexpr auto iter(Int<i>) -> decltype(iter(Int<i-1>{}));
      
      int main(){
        decltype(iter(Int<10>{})) a;
      }
      
    • Это хорошо работает, поскольку вы можете видеть здесь:

      template<int i> struct Int {};
      
      constexpr auto iter(Int<0>) -> Int<0>;
      
      template<int i>
      constexpr auto iter(Int<i>) -> decltype(auto) {
        return iter(Int<i-1>{});
      }
      
      int main(){
        decltype(iter(Int<10>{})) a;
      }
      

decltype(expr):

  • применяет SFINAE, а decltype(auto) не

Ответ 3

Еще один пример. В случае decltype(auto) допускаются лямбды. В случае decltype(expr) lambdas запрещены.

Это потому, что a) lambdas не допускаются как неоцененные операнды (например, в sizeof или decltype) и b) тип в decltype и в теле двух выражений будет другим, так как они являются двумя различными лямбда-выражениями

// ill-formed: lambda within decltype
auto f() -> decltype([]{}) { return []{}; };

// allowed: lambda is not in decltype, and only appears once
auto f() -> decltype(auto) { return []{}; }