Безопасный, совместимый со стандартами способ заставить специализацию шаблона класса не скомпилироваться с использованием `static_assert`, только если он создан? - PullRequest
8 голосов
/ 16 июня 2019

Предположим, что мы хотим создать шаблонный класс, который может быть создан только с номерами и не должен компилироваться иначе.Моя попытка:

#include <type_traits>

template<typename T, typename = void>
struct OnlyNumbers{
public:
    struct C{};
    static_assert(std::is_same<C,T>::value, "T is not arithmetic type.");

    //OnlyNumbers<C>* ptr;
};

template<typename T>
struct OnlyNumbers<T, std::enable_if_t<std::is_arithmetic_v<T>>>{};

struct Foo{};
int main()
{
    OnlyNumbers<int>{}; //Compiles
    //OnlyNumbers<Foo>{}; //Error
}

Демонстрация в реальном времени - Кажется, что все три основных компилятора работают должным образом.Мне известно, что уже есть похожий вопрос с ответами, цитирующими стандарт.Принятый ответ использует temp.res.8 вместе с temp.dep.1 для ответа на этот вопрос.Я думаю, что мой вопрос немного отличается, потому что я спрашиваю именно о моем примере, и я не уверен в мнении стандарта по этому поводу.

Я бы сказал, что моя программа не является некорректной и что онане может быть скомпилирован тогда и только тогда, когда компилятор пытается создать базовый шаблон.Мои рассуждения:

  • [temp.dep.1]:

    Внутри шаблона некоторые конструкции имеют семантику, которая может различаться в разных экземплярах.Такая конструкция зависит от параметров шаблона.

    Это должно сделать std::is_same<C,T>::value зависимым от T.

  • [temp.res.8.1]:

    никакая действительная специализация не может быть сгенерирована для шаблона или подстановки constexpr, если оператор внутри шаблона и шаблона не создан, или

    не применяется, потому что существует допустимая специализация, в частности OnlyNumbers<C> допустимо и может использоваться внутри класса, например, для определения переменной указателя члена (ptr).Действительно, удаляя assert и раскомментируя строки ptr, OnlyNumbers<Foo>, код компилируется.

  • [temp.res.8.2 - 8.4] не применяется.

  • [temp.res.8.5] Я тоже не думаю, что это применимо, но я не могу сказать, что полностью понимаю этот раздел.

Мой вопрос: мои рассуждения верны?Является ли это безопасным, совместимым со стандартом способом заставить конкретный шаблон [class] * не скомпилироваться с использованием static_assert, если ** и только если он создан?

* В первую очередь меня интересуют шаблоны классовНе стесняйтесь включать шаблоны функций.Но я думаю, что правила одинаковы.

** Это означает, что не существует T, который можно использовать для создания шаблона извне, как T=C можно использовать изнутри.Даже если к C можно как-то получить доступ, я не думаю, что есть способ сослаться на него, потому что это приводит к этой рекурсии OnlyNumbers<OnlyNumbers<...>::C>.

РЕДАКТИРОВАТЬ:

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

Ответы [ 3 ]

2 голосов
/ 17 июня 2019

Статические утверждения можно использовать непосредственно в классе, не делая ничего сложного.

#include <type_traits>

template<typename T>
struct OnlyNumbers {
    static_assert(std::is_arithmetic_v<T>, "T is not arithmetic type.");
    // ....
};

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

Время от времени я использовал один трюк

#include <type_traits>

template<typename T>
struct OnlyNumbers {
    static_assert(std::is_arithmetic_v<T>, "T is not arithmetic type.");
    using TT = std::conditional_t<std::is_arithmetic_v<T>,T,int>;
    // ....
};

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

1 голос
/ 17 июня 2019

Ваш код неправильно сформирован, так как первичный шаблон не может быть создан. См. Стандартную цитату в ответе Барри на связанный с вами вопрос. Обходной путь, который вы использовали, чтобы гарантировать, что четко установленное стандартное требование не может быть выполнено , не помогает. Хватит драться с вашим компилятором RSP. стандарт, и идти с подходом Handy999. Если вы все еще не хотите этого делать, например, по СУХИМ причинам, подходящим способом достижения вашей цели будет:

template<typename T, typename Dummy = void>
struct OnlyNumbers{
public:
    struct C{};
    static_assert(! std::is_same<Dummy, void>::value, "T is not a number type.");

Два замечания:

  • Во-первых, я намеренно заменил сообщение об ошибке, потому что сообщение об ошибке "не арифметического типа" кричит, что вы должны проверить ! std::is_arithmetic<T>::value. Подход, который я обрисовал в общих чертах, потенциально имеет смысл, если у вас есть несколько перегрузок для «числовых» типов, некоторые из которых соответствуют требованиям арифметического типа стандарта, а другие могут не соответствовать (например, может быть тип из многоточечной библиотеки).
  • Во-вторых, вы можете возразить, что кто-то может написать, например, OnlyNumbers<std::string, int> чтобы победить статическое утверждение. На что я говорю, это их проблема. Помните, что каждый раз, когда вы делаете что-то идиотское доказательство, природа делает лучшего идиота. ;-) Серьезно, do делает API-интерфейсы простыми в использовании и трудными для злоупотребления, но вы не можете исправить безумие и не должны беспокоиться о попытках.

TL; DR: KISS и SWYM (скажи, что ты имеешь в виду)

1 голос
/ 16 июня 2019

Ну ... Я не понимаю, что вы имеете в виду под

[[temp.res.8.1]] Не применяется, потому что существует действительная специализация, в частности, допустимы только OnlyNumbersи может использоваться внутри класса, например, для определения переменной указателя члена (ptr).

Можете ли вы привести пример OnlyNumers действительного и компилирующего основного шаблона на основе OnlyNumbers<C>?

В любом случае, мне кажется, что дело именно в этом.

Если вы спросите

Это безопасный, совместимый со стандартами способ заставить конкретный шаблон [class] * не скомпилироваться с использованием static_assert, если ** и только если он создан?

мне кажется, что (возможно, исключая тест, который является верным, только если другая специализация соответствует), ответ "нет" из-за [temp.res.8.1].

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

Например, вы можете добавить третий параметр шаблона с другим значением по умолчанию и что-то ещеследующим образом

template<typename T, typename U = void, typename V = int>
struct OnlyNumbers
 {
   static_assert(std::is_same<T, U>::value, "test 1");
   static_assert(std::is_same<T, V>::value, "test 2");
 };

Таким образом, вы открываете дверь для законной инстанции

OnlyNumbers<Foo, Foo, Foo>     o1;
OnlyNumbers<void, void, void>  o2;
OnlyNumbers<int, int>          o3;

, но эксплицируете хотя бы второй тип шаблона.

В любом случае, зачемВы просто избегаете определятьосновная версия шаблона?

// declared but (main version) not defined
template<typename T, typename = void>
struct OnlyNumbers;

// only specialization defined
template<typename T>
struct OnlyNumbers<T, std::enable_if_t<std::is_arithmetic_v<T>>>
 { };
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...