Каков наилучший способ создания шаблона функции только для специализации? - PullRequest
9 голосов
/ 29 ноября 2011

Есть ли лучший способ сделать следующее?

#include <iostream>

template <typename T>
T Bar();

template <>
int Bar<int>() { return 3; }

// Potentially other specialisations

int main()
{
    std::cout << Bar<int>() << std::endl; // This should work
    std::cout << Bar<float>() << std::endl; // This should fail
}

Проблема с этим решением состоит в том, что оно терпит неудачу во время (понятно) связи с «неопределенной ссылкой на float Bar<float>()» или подобным. Это может сбивать с толку других разработчиков, так как они могут подозревать, что файл реализации не связан.

Я знаю другое потенциальное решение:

template <typename T>
T Bar() { BOOST_STATIC_ASSERT(sizeof(T) == 0); }

Это вызывает ошибку компилятора при запросе Bar<float>(), именно то, что я хочу. Тем не менее, я обеспокоен тем, что технически компилятор может отклонить это так же, как gcc отклоняет BOOST_STATIC_ASSERT(false), потому что он знает, что потерпит неудачу независимо от параметра шаблона, поскольку sizeof(T) может никогда быть нулевым.

В целом, я хочу знать:

  1. Есть еще один способ сделать это.
  2. Я ошибаюсь и BOOST_STATIC_ASSERT(sizeof(T)) на самом деле не может потерпеть неудачу без инстанцирования.
  3. Единственный способ - разрешить эту ошибку компоновщика, как указано выше.

Ответы [ 5 ]

6 голосов
/ 29 ноября 2011

Это может сработать:

template <typename T>
T Bar() {
  T::ERROR_invalid_template_argument_;
}

template <>
int Bar<int>() { return 3; }

Вы также можете использовать максимально возможный размер, если боитесь использовать 0:

  static_assert(sizeof(T) == -1, "No specialization");
4 голосов
/ 29 ноября 2011

BOOST_STATIC_ASSERT(sizeof(T) == 0); не допускается сбой до тех пор, пока не будет создан экземпляр шаблона, поэтому я бы просто сделал это. Вы правы, что BOOST_STATIC_ASSERT(false); срабатывает каждый раз.


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

Вот почему BOOST_STATIC_ASSERT(false); всегда будет терпеть неудачу: здесь ничего не зависит, и утверждение обрабатывается немедленно, как если бы функция вообще не была шаблоном. (Обратите внимание, что MSVC не реализует двухфазный поиск, поэтому это неправильно при создании экземпляра.) Наоборот, потому что T является зависимым (§14.6.2.1), BOOST_STATIC_ASSERT(sizeof(T) == 0); является зависимым и не может быть проверяется, пока не будет создан экземпляр шаблона. (Где это всегда будет терпеть неудачу.)

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

BOOST_STATIC_ASSERT(sizeof(typename T::please_use_specializations) == 0);

Это гарантированно приведет к сбою, и компилятору не удастся правильно «умно» выйти из строя раньше времени.

0 голосов
/ 09 декабря 2011

Есть небольшое предостережение, если вы используете gcc без -pedantic, в этом случае - это возможно иметь sizeof(T) == 0 - когда T - массив нулевой длины.

#include <iostream>

#include "boost/static_assert.hpp"

template <typename T>
void Foo()
{
    BOOST_STATIC_ASSERT(sizeof(T) == 0);
    std::cout << "Actually, it is possible to instantiate this." << std::endl;
}

int main()
{
    Foo<int[0]>();

    return 0;
}

В этом случае вы можете обойти это, используя вместо этого:

BOOST_STATIC_ASSERT(sizeof(T) == sizeof(T) + 1);

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

#define NEVER_INSTANTIATE(T) BOOST_STATIC_ASSERT(sizeof(T) == sizeof(T) + 1);

Как объяснил GMan, это не может произойти без создания экземпляра, как sizeof(T) == 0. Однако мораль этой истории, вероятно, вместо этого должна быть всегда компилируемой с -pedantic .

0 голосов
/ 29 ноября 2011

использовать static_assert с c ++ 0x

template <typename T> 
void bar(){
 static_assert(false, " invalid argument type");
}

, это приведет к ошибке при компиляции.

Для c ++ 98/2003 мы могли бы попробовать этот массив

template <typename T> 
void bar(){
char invalid_arg_[0];
}

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

Первое не будет выбором, поскольку оно всегда терпит неудачу.

0 голосов
/ 29 ноября 2011

Вы можете сделать что-то вроде следующего:

template <typename T>
T Bar()
{ T::unspecialized_method_called; }

Это, конечно, предполагает, что T не имеет члена с заданным именем, поэтому вам придется соответствующим образом выбрать свое «сообщение об ошибке» (например, нарушив соглашения об именах).

...