Обеспечение отсутствия дубликатов в вариативном шаблоне

Я думаю, что вся моя проблема хорошо описана в заголовке. Я пытаюсь создать шаблон класса с переменным числом аргументов (на С++ 11, С++ 14 или С++ 1z).

template<typename ...Types> struct MyVariadicTemplate {};

и убедитесь, что список типов в любом экземпляре MyVariadicTemplate является инъективным, поэтому, если я, например, вызову следующий фрагмент кода:

MyVariadicTemplate<int, double, int> x;

он не будет компилироваться (я был бы рад сделать это как-нибудь с помощью static_assert).

Буду признателен за подсказку.


person Jytug    schedule 06.12.2015    source источник


Ответы (3)


Это можно записать с помощью двух метафункций.

Во-первых, IsContained проверяет, присутствует ли тип в списке типов.

template <typename T, typename... List>
struct IsContained;

template <typename T, typename Head, typename... Tail>
struct IsContained<T, Head, Tail...>
{
    enum { value = std::is_same<T, Head>::value || IsContained<T, Tail...>::value };
};

template <typename T>
struct IsContained<T>
{
    enum { value = false };
};

Во-вторых, IsUnique проверяет, не содержит ли список типов дубликатов. Он использует IsContained для проверки всех комбинаций элементов.

template <typename... List>
struct IsUnique;

template <typename Head, typename... Tail>
struct IsUnique<Head, Tail...>
{
    enum { value = !IsContained<Head, Tail...>::value && IsUnique<Tail...>::value };
};

template <>
struct IsUnique<>
{
    enum { value = true };
};

С помощью этих инструментов статическое утверждение становится очень простым:

template <typename... Ts>
struct NoDuplicates
{
    static_assert(IsUnique<Ts...>::value, "No duplicate types allowed");
};
person TheOperator    schedule 06.12.2015
comment
Вау, это умно. Я только что видел факторный пример, но мне не пришло в голову применить его. Это пригодится и в моей дальнейшей работе, потому что IsContained мне все равно бы понадобился. Спасибо - person Jytug; 07.12.2015
comment
Я люблю enum { value = ... } вместо static constexpr auto value = ... - person Joel Cornett; 07.12.2015

Если у вас есть доступ к выражениям свертки C++1z, вы можете упростить ответ @TheOperator, выполнив следующие действия для IsContained:

template <typename T, typename... List>
struct IsContained;

template <typename T, typename Head, typename... Tail>
struct IsContained<T, Head, Tail...>
{
    enum { value = std::is_same<T, Head>::value || (IsContained<T, Tail>::value && ... && true) };
};

template <typename T>
struct IsContained<T>
{
    enum { value = false };
};

Разница в том, что с выражением fold больше шансов на повторное использование экземпляров класса. Если вы используете много параметров или имеете много повторных сравнений, это может иметь более быстрое время компиляции. Как всегда, проверьте это сами.

person JKor    schedule 06.12.2015
comment
Спасибо. Однако из любопытства - нужно ли добавлять ( && true) в конце выражения сгиба? Здесь en.cppreference.com/w/cpp/language/fold упоминается что складка && оценивается как истина при пустой пачке. - person Jytug; 07.12.2015
comment
@Jytug на недавних встречах по стандартизации они удалили все значения по умолчанию для унарных сгибов и сделали унарные складки с плохо сформированным пустым пакетом (см. P0160R0). Вот почему я сделал двоичную складку (с && true) - person JKor; 07.12.2015
comment
(IsContained‹T, Tail›::value && ... && true) это неверно! Вместо этого используйте (IsContained‹T, Tail›::value || ... || false), потому что результат выражения fold должен быть истинным в случае, если какое-либо значение IsContained‹T, Tail›::value истинно. - person sigmaN; 03.05.2020

С С++ 17 это довольно просто.

#include <type_traits>

template <typename T, typename... Ts>
struct are_distinct: std::conjunction<
    std::negation<std::is_same<T, Ts>>...,
    are_distinct<Ts...>
>{};

template <typename T>
struct are_distinct<T>: std::true_type{};

Алгоритм такой же, как показано на Jkor и TheOperator, но гораздо более лаконично.

Затем, переходя к вашему конкретному вопросу, вы должны использовать его следующим образом:

template<typename ...Types> 
struct MyVariadicTemplate {
    static_assert(are_distinct<Types...>::value, "Types must be distinct");

    /* rest of the code */
};

Однако, учитывая, что это O (n²), интересно, есть ли способ сделать его менее алгоритмически сложным? Boost::mpl уникален‹› O( n), но я не могу понять алгоритм, который он использует.

person Fabio A.    schedule 21.03.2021