Является ли `std:: common_type` ассоциативным?

Класс шаблона std::common_type вычисляет общий тип для списка вариационного типа. Он определяется с помощью возвращаемого типа тернарного оператора x:y?z рекурсивно. Из этого определения мне не очевидно, рассчитывает ли a std::common_type<X,Y> ассоциативно, т.е. е. ли

using namespace std;
static_assert( is_same<common_type< X, common_type<Y,Z>::type    >::type,
                       common_type<    common_type<X,Y>::type, Z >::type>::value, "" );

никогда не будет вызывать ошибку времени компиляции для всех типов X, Y и Z, для которых справедливо выражение is_same<...>.

Обратите внимание, что я не спрашиваю,

static_assert( is_same<common_type<X,Y>::type,
                       common_type<Y,X>::type>::value, "" );

будет когда-либо стрелять. Это, очевидно, не будет. Вышеупомянутый вопрос совершенно другой.

Обратите внимание, что спецификация std::common_type слегка изменилась в С++ 14 и, вероятно, снова изменится на С++ 17. Таким образом, ответы могут отличаться для разных версий стандарта.

Ответы

Ответ 1

Это не работает на MinGW-w64 (gcc 4.9.1). Также сбой на VS2013 и (спасибо Baum mit Augen) на gcc5.2 или clang 3.7 с libС++.

#include <type_traits>

using namespace std;

struct Z;
struct X{operator Z();};
struct Y{operator X();};
struct Z{operator Y();};

static_assert( is_same<common_type<X,Y>::type,
                       common_type<Y,X>::type>::value, "" ); // PASS

static_assert( is_same<common_type<X,Z>::type,
                       common_type<Z,X>::type>::value, "" ); // PASS

static_assert( is_same<common_type<Y,Z>::type,
                       common_type<Z,Y>::type>::value, "" ); // PASS

static_assert( is_same<common_type< X, common_type<Y,Z>::type    >::type,
                       common_type<    common_type<X,Y>::type, Z >::type>::value, "" ); // FAIL...

Ответ 2

#include <type_traits>

struct T2;
struct T1 {
    T1(){}
    T1(int){}
    operator T2();
};
struct T2 {
    operator int() { return 0; }
};
struct T3 {
    operator int() { return 0; }
};
T1::operator T2() { return T2(); }

using namespace std;
using X = T1;
using Y = T2;
using Z = T3;
int main()
{

    true?T2():T3(); // int
    static_assert(std::is_same<std::common_type_t<T2,
                                                  T3>,
                               int>::value,
                  "Not int");

    true?T1():(true?T2():T3()); // T1
    static_assert(std::is_same<std::common_type_t<T1,
                                                  std::common_type_t<T2,
                                                                     T3>>,
                               T1>::value,
                  "Not T1");

    // -----------------------------------------

    true?T1():T2(); // T2
    static_assert(std::is_same<std::common_type_t<T1,
                                                  T2>,
                               T2>::value,
                  "Not T2");

    true?(true?T1():T2()):T3(); // int
    static_assert(std::is_same<std::common_type_t<std::common_type_t<T1,
                                                                     T2>,
                                                  T3>,
                               int>::value,
                  "Not int");

    // -----------------------------------------

    static_assert( is_same<common_type_t< X, common_type_t<Y,Z>    >,
                           common_type_t<    common_type_t<X,Y>, Z > >::value,
                    "Don't match");
}

Ой! Психическая гимнастика здесь повредила мне голову, но я придумал случай, который не удалось скомпилировать, напечатав "Не совпадать" с gcc 4.9.2 и с "С++ 14" (gcc 5.1) на ideone. Теперь, независимо от того, соответствует ли это, другое дело...

Теперь претензия относится к типам классов, std::common_type_t<X, Y> должна быть либо X, либо Y, но я принуждаю std::common_type_t<T2, T3> к преобразованию в int.

Попробуйте с другими компиляторами и дайте мне знать, что произойдет!

Ответ 3

Это не ассоциативно! Здесь программа, в которой он терпит неудачу:

#include <type_traits>

struct Z;
struct X { X(Z); }; // enables conversion from Z to X
struct Y { Y(X); }; // enables conversion from X to Y
struct Z { Z(Y); }; // enables conversion from Y to Z

using namespace std;    
static_assert( is_same<common_type< X, common_type<Y,Z>::type    >::type,
                       common_type<    common_type<X,Y>::type, Z >::type>::value, 
               "std::common_type is not associative." );

Идея проста: на следующей диаграмме показано, что common_type вычисляет:

    X,Y -> Y
    Y,Z -> Z
    X,Z -> X

Первая строка логична, так как X может быть преобразована в Y, но не наоборот. То же самое для двух других линий. Когда X и Y объединяются и рекомбинируются с помощью Z, получаем Z. С другой стороны, объединение Y и Z и объединение X с результатом дает X. Поэтому результаты разные.

Основной причиной этого является то, что конвертируемость не является транзитивной, т.е. е. если X конвертируется в Y и Y, конвертируемый в Z, из этого не следует, что X можно конвертировать в Z. Если конвертируемость была транзитивной, то конверсии работали бы в обоих направлениях, и, следовательно, common_type нельзя было бы однозначно вычислять и приводить к ошибке времени компиляции.

Это рассуждение не зависит от стандартной версии. Это относится к С++ 11, С++ 14 и предстоящему С++ 17.