Почему у нас должен быть поздний тип возврата? - PullRequest
2 голосов
/ 05 мая 2011

Взгляните на это:

template<class T>
struct X
{
private:
    T value_;
public:
    X():value_(T()) {}
    X(T value):value_(value) {}

    T getValue() const
    {
        return value_;
    }

    static const T value = 0; //this is dummy

    template<class D, class U>
    friend decltype(X<D>::value + X<U>::value) operator + (X<D> lhs, X<U> rhs);
};

template<class T, class U>
decltype(X<T>::value + X<U>::value) operator + (X<T> lhs, X<U> rhs)
{
    return lhs.getValue() + rhs.getValue();
    /* THIS COMPILES WITH VS2010 but line below doesn't which is incorrect
     * behaviour in my opinion because: friendship has been declared and
     * access to private data should be granted.*/
    return lhs.value_ + rhs.value_; //THIS COMPILES WITH GCC 4.6
}

И после такого примера должен возникнуть вопрос (кстати, весь пример компилируется и работает как задумано), в любом случае, вот вопрос:

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

Отредактировано (теперь без этой страшной статичной куклы - все поют, все танцуют)

template<class T>
struct X
{
private:
    T value_;
public:
    typedef T value_type;

    X():value_(T()) {}
    X(T value):value_(value) {}
    T getValue()const { return value_; }

    template<class D, class U>
    friend X<decltype(typename X<D>::value_type() + typename X<U>::value_type())> operator + (X<D> lhs, X<U> rhs);
};

template<class T, class U>
X<decltype(typename X<T>::value_type() + typename X<U>::value_type())> operator + (X<T> lhs, X<U> rhs)
{
    return lhs.getValue() + rhs.getValue();
    //return lhs.value_ + rhs.value_; //VS is __wrong__ not allowing this code to compile
}

Ответы [ 5 ]

3 голосов
/ 05 мая 2011

Все это не хватает лучшей читабельности, которую он добавляет, без необходимости использовать typedefs или шаблоны псевдонимов

auto f() -> void(*)();

Сравните это с эквивалентом

void (*f())();

Вы также можете получить доступ к this в позднем указанном типе возврата, но не в раннем типе возврата

class A {
  std::vector<int> a;
public:
  auto getVector() -> decltype((a));
  auto getVector() const -> decltype((a));
};

Если бы у вас было наоборот, это не сработало бы, потому что this не было бы в области видимости, и decltype((a)) будет иметь один и тот же тип оба раза (неявный this не будет добавлен, поэтому тип this не может влиять на тип (a)).

3 голосов
/ 05 мая 2011

Иногда это просто не работает без конечного возвращаемого типа, потому что у компилятора нет возможности узнать, что программист просит его сделать или какие типы задействованы.

Возьмем эту простую шаблонную функцию переадресации (это не выдуманный пример, а взятый из некоторого реального кода, который я недавно написал):

template<typename T, typename... A> auto fwd(T fp, A...a) -> decltype(fp(a...))
{
    // some other code
    return fp(a...);
};

Эта функция может вызываться с любым указателем неизвестного типа, который имеет любой тип неизвестного возвращаемого типа. Это сработает, вернет правильный тип и не смущает компилятор.

Без завершающего типа возвращаемого значения компилятор не смог бы выяснить, что происходит.

Вы можете получить аналогичный эффект с #define и злоупотреблением оператором запятой, но да ... по какой цене.

2 голосов
/ 05 мая 2011

Это можно сделать без decltype, но это имеет некоторые недостатки. Вам либо нужен дополнительный параметр шаблона, либо вы должны полагаться на соглашение, согласно которому при добавлении двух элементов типа T создается элемент типа T.

1 голос
/ 05 мая 2011

У меня нет этого вопроса, вы спрашиваете, почему язык позволяет вам делать:

template <typename T, typename U>
auto foo( X<T> lhs, X<U> rhs ) -> decltype( lhs + rhs );

Вместо принуждение к искусственному добавлению статического фиктивного члена в шаблон X, чтобы вы могли ввести:

template <typename T, typename U>
decltype( X<T>::unneeded_artificial_static_member +
          X<U>::unneeded_artificial_static_member )
foo( X<T> lhs, X<U> rhs ) -> decltype( lhs + rhs );

Серьезно? Я имею в виду, что вы можете использовать другие менее обременительные конструкции, например:

template <typename T>
T& lvalue_of();

template <typename T, typename U>
decltype( lvalue_of< X<T> > + lvalue_of< X<U> > )
foo( X<T> lhs, X<U> rhs );

А затем вынудите использовать определенные варианты lvalue_of при необходимости:

template <typename T, typename U>
decltype( lvalue_of< X<T> > + lvalue_of< X<U> const > )
foo( X<T> lhs, X<U> const & rhs );

А затем создайте дополнительные варианты для rvalue_ref ... для каждого потенциального варианта использования, который может возникнуть, но с какой стати вы бы предпочли, чтобы стандарт заставлял вас подвергаться таким странным ошибкам (не забудьте обновить decltype если вы изменяете аргумент?) Конструкции для определения типа, когда после объявления аргумента компилятор может так легко сделать это самому безопасным простым безопасным способом?

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

std::function< void () > f = [](){ std::cout << "Hi"; };

может быть легко реализовано как:

struct lambda {
   void operator()() {
      std::cout << "Hi";
   }
};
std::function< void () > f = lambda();

И тогда вы, вероятно, можете подумать о большом количестве других функций, которые так же легко можно исключить из языка, поскольку есть обходные пути.

1 голос
/ 05 мая 2011

Если я правильно вас понимаю, под «поздним типом возврата» вы подразумеваете что C ++ 11 называет конечным типом возврата. В случае, если вы В настоящее время проблем нет, и если вы не хотите использовать конечный тип возврата, нет причин для этого. Если возвращение Тип зависит от типа аргумента, однако может быть чрезвычайно многословно, чтобы повторить их в decltype:

template <typename T1, typename T2>
auto add( T1 lhs, T2 rhs ) -> decltype( lhs + rhs );

Чтобы не использовать конечный тип возврата, вам нужно написать что-то вроде:

template <typename T1, typename T2>
decltype( (*(T1*)0 + *(T2*)0 ) add( T1 lhs, T2 rhs );

Намного яснее, какой тип возврата в первом примере, и если типы параметров являются более сложными (например, что-то как std::vector<T1>), это также намного более кратко:

template <typename T>
auto findInVector( std::vector<T> const& v ) -> decltype( v.begin() );

против

template <typename T>
typename std::vector<T>::const_iterator
        findInVector( std::vector<T> const& v );
...