WedX - журнал о программировании и компьютерных науках

Почему нельзя передать структуру как значение как параметр, не являющийся типом шаблона?

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

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

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

Меня беспокоит одна вещь: структуры. Учитывать:

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;

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

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

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

09.04.2013

  • Я думаю, что стандарт допускает только указатели на int и элементы. У вас не может быть даже double или float в качестве параметров, не являющихся типами. Или даже const char *, которые все были полезны (и на удивление работали на VC6). Не уверен, что что-то изменилось для C ++ 11, судя по некоторым ответам. 09.04.2013
  • @Pete const char* имеет проблему неравенства между одними и теми же литералами, написанными в разных местах, одно и то же остается для double / float. Кроме того, вы пропустили ссылки. Мой вопрос был не в том, возможно ли это (очевидно, нет), а скорее в том, почему это не так. 09.04.2013
  • Я предполагаю, что потенциально было слишком много возможностей привести что-либо в соответствие со стандартом. Его гораздо проще указать, если он ограничен только int. Возможно, через 50 лет он будет доступен в следующем стандарте C ++. 09.04.2013
  • Почему должно быть неравенство для float / double? 09.04.2013
  • @Pete Проверяете ли вы две переменные с плавающей запятой на равенство, используя a == b? 09.04.2013
  • Это зависит от контекста - если у вас есть константа с плавающей запятой, вы ожидаете, что она будет равна самой себе и будет иметь точно такой же двоичный макет. Сравнение чисел с плавающей запятой / двойных чисел на равенство с нулем с целью предотвращения деления на ноль может быть разумным делом. Если это результат математической операции, например sin (), то вы ожидаете использовать нечеткое сравнение. 09.04.2013
  • как говорится в сообщении об ошибке, Triple {1,2,3} действительно не является lvalue. вы пробовали &Triple {1,2,3}? 09.04.2013
  • По той же причине не существует неявно определенного operator==? 28.06.2013
  • @curiousguy нужно уточнить, почему он не определен неявно? Возможно, подойдет какая-нибудь ссылка. 28.06.2013
  • @BartekBanachewicz У меня сейчас нет ссылки, причина в том, что программисты действительно должны говорить, что они имеют в виду (поэлементное сравнение не всегда хорошо), и в C также нет неявного сравнения структур (но есть копии структур в C) так что нет аргумента совместимости C. На практике копирование требуется большему количеству классов, чем сравнению, и поэлементное копирование чаще бывает правильным, чем поэлементное сравнение. 28.06.2013
  • @curiousguy и побочное копирование чаще бывает правильным, чем пословное сравнение. Я бы сказал иначе. 28.06.2013
  • @BartekBanachewicz F.ex. для класса fraction побочное копирование - это нормально, а пословное сравнение - нет (если вы не всегда нормализуете). Что вы имеете в виду в качестве типичного встречного примера? 28.06.2013
  • @curiousguy все, что управляет памятью или идентификатором. Последовательное копирование почти всегда неверно. 28.06.2013
  • @BartekBanachewicz 1) Вы должны использовать такие инструменты, как string и контейнеры STL для обработки памяти. 2) Для чего-либо, содержащего указатели, если пословное копирование неверно (т.е. если это не итератор), то побочное сравнение тоже выполняется! 28.06.2013

Ответы:


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; }
};
09.04.2013
  • meta_triple - потрясающее творение. И это действительно хорошо решает мои проблемы. 09.04.2013
  • Можете ли вы создать функцию, работающую в другом направлении, которая преобразует Triple в meta_triple? 25.10.2016
  • @ Эрик, нет, это дорога с односторонним движением. 26.10.2016

  • 2

    Обновленный ответ для c ++ 20 пользователи:

    В C ++ 20 добавлена ​​поддержка не типовых параметров шаблона литерала класса (класс с конструктором constexpr), что позволит использовать пример в исходном вопросе при условии, что параметр шаблона принимается по значению:

    template<Triple t> // Note: accepts t by value
    class Foo { };
    
    // Works with unnamed instantiation of Triple.
    Foo<Triple { 1, 2, 3 }> f1 {};
    
    // Also works if provided from a constexpr variable.
    constexpr Triple t { 1, 2, 3 };
    Foo<t> f2 {};
    

    Кроме того, все экземпляры параметров шаблона Triple { 1, 2, 3 } в программе будут ссылаться на один и тот же объект статической продолжительности хранения:

    template<Triple t1, Triple t2>
    void Func() {
        assert(&t1 == &t2); // Passes.
    }
    
    constexpr Triple t { 1, 2, 3 };
    
    int main()
    {
        Func<t, Triple {1, 2, 3}>();
    }
    

    Из cppreference:

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

    Обратите внимание, что существует довольно много ограничений на типы литералов класса, допустимые параметрами шаблона. Для получения дополнительных сведений ознакомьтесь с этим сообщением в блоге, которое я написал с объяснением использования и ограничений NTTP-файлов буквального класса: Литеральные классы как параметры шаблона, не являющиеся типами в C ++ 20.

    16.02.2020
  • О, это просто потрясающе! Спасибо, что повторно посетили этот вопрос с таким отличным постом. 17.02.2020

  • 3

    Вы можете определить 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;
    

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

    09.04.2013
  • Хм, так что все сводится к тому факту, что в языке нет тривиальных op== для структур POD, и, таким образом, две разные структуры остаются разными в контексте шаблона, даже если у них внутри точно такие же данные. 09.04.2013
  • Новые материалы

    Объяснение документов 02: BERT
    BERT представил двухступенчатую структуру обучения: предварительное обучение и тонкая настройка. Во время предварительного обучения модель обучается на неразмеченных данных с помощью..

    Как проанализировать работу вашего классификатора?
    Не всегда просто знать, какие показатели использовать С развитием глубокого обучения все больше и больше людей учатся обучать свой первый классификатор. Но как только вы закончите..

    Работа с цепями Маркова, часть 4 (Машинное обучение)
    Нелинейные цепи Маркова с агрегатором и их приложения (arXiv) Автор : Бар Лайт Аннотация: Изучаются свойства подкласса случайных процессов, называемых дискретными нелинейными цепями Маркова..

    Crazy Laravel Livewire упростил мне создание электронной коммерции (панель администратора и API) [Часть 3]
    Как вы сегодня, ребята? В этой части мы создадим CRUD для данных о продукте. Думаю, в этой части я не буду слишком много делиться теорией, но чаще буду делиться своим кодом. Потому что..

    Использование машинного обучения и Python для классификации 1000 сезонов новичков MLB Hitter
    Чему может научиться машина, глядя на сезоны новичков 1000 игроков MLB? Это то, что исследует это приложение. В этом процессе мы будем использовать неконтролируемое обучение, чтобы..

    Учебные заметки: создание моего первого пакета Node.js
    Это мои обучающие заметки, когда я научился создавать свой самый первый пакет Node.js, распространяемый через npm. Оглавление Глоссарий I. Новый пакет 1.1 советы по инициализации..

    Забудьте о Matplotlib: улучшите визуализацию данных с помощью умопомрачительных функций Seaborn!
    Примечание. Эта запись в блоге предполагает базовое знакомство с Python и концепциями анализа данных. Привет, энтузиасты данных! Добро пожаловать в мой блог, где я расскажу о невероятных..


    Для любых предложений по сайту: [email protected]