Почему не может быть передана структура как значение в качестве параметра шаблона не-типа?

Параметры шаблона непигового типа, очевидно, являются типами, которые не являются типами, например:

template<int x>
void foo() { cout << x; }

В этом случае есть другие варианты, кроме int, и я хотел бы назвать этот отличный ответ.

Теперь есть одна вещь, которая меня пугает: structs. Рассмотрим:

struct Triple { int x, y, z; };

Triple t { 1, 2, 3 };

template<Triple const& t>
class Foo { };

Теперь, используя обычную нетопическую ссылочную семантику, мы можем написать:

Foo<t> f;

Что стоит отметить, что t не может быть constexpr или даже const, потому что это подразумевает внутреннюю привязку, что в основном означает, что строка не будет компилироваться. Мы можем обойти это, объявив t как const extern. Это может быть немного странно, но тот, который действительно заставило меня задуматься, почему это невозможно:

Foo<Triple { 1, 2, 3 }> f;

Мы получаем очень приличную ошибку от компилятора:

error: Triple{1, 2, 3} не является допустимым аргументом шаблона для типа const Triple&, потому что это не значение lvalue.

Мы не можем указать Triple в шаблоне по значению, потому что это запрещено. Однако я не понимаю реальной проблемы с этой небольшой строкой кода. Какова причина отсутствия возможности использования структур как значений. Если я могу использовать три int s, почему бы не создать структуру из трех целых чисел? Если он имеет только тривиальные специальные элементы, он не должен быть действительно различным в обработке, чем просто три переменные.

Ответ 1

Было бы легко заставить этот бит работать, но тогда люди будут жаловаться на то, что использование параметров структуры шаблона не работает во всех тех же ситуациях, что и другие параметры шаблона (рассмотрим частичную специализацию или что делать с operator==).

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

template <int X, int Y, int Z>
struct meta_triple {
    // static value getters
    static constexpr auto x = X;
    static constexpr auto y = Y;
    static constexpr auto z = Z;
    // implicit conversion to Triple 
    constexpr operator Triple() const { return { X, Y, Z }; }
    // function call operator so one can force the conversion to Triple with
    // meta_triple<1,2,3>()()
    constexpr Triple operator()() const { return *this; }
};

Ответ 2

Вы можете определить t как const extern, давая ему внешнюю привязку. Затем работает конструкция:

struct Triple { int x, y, z; };

const extern Triple t { 1, 2, 3 };

template<Triple const& t>
class Foo { };

Foo<t> f;

Живой пример.

Причина, по которой вы не можете передать временный параметр ссылочного шаблона, заключается в том, что параметр является ссылкой.. Вы получите ту же ошибку, если параметр шаблона был const int&, и вы попытался пройти 7. Пример.

ИЗМЕНИТЬ

Разница между тремя int и структурой, содержащей три int, состоит в том, что все литералы типа int действительно являются одним и тем же значением (все вхождения 7 составляют всего семь), а каждый вызов конструктора структура концептуально создает новый экземпляр. Возьмите этот гипотетический пример:

template <Triple t>
struct Foo {};

Foo<Triple {1, 2, 3}> f1;
Foo<Triple {1, 2, 3}> f2;

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