Ошибка принудительной компиляции при вызове специализированной шаблонной функции

#c #templates #compiler-errors #partial-specialization

#c #шаблоны #ошибки компилятора #частичная специализация

Вопрос:

У меня есть шаблонная функция. Он имеет четко определенную семантику, если аргумент не является типом указателя. Если кто-то вызывает эту функцию, передавая аргумент типа pointer, я хочу принудительно выдать ошибку времени компиляции. У меня нет проблем с написанием общего (легального) шаблона и соответствующей частично специализированной (нелегальной) версии. Я просто не могу понять, как перенести ошибку с определения функции на вызов функции.

Комментарии:

1. язык? c 98? c 03? c 11? доступны библиотеки compiler Boost / TR1?

2. приветствия (в отделе «лучше поздно, чем никогда»?) Я понял это из исчезающего accept. Нет проблем!

Ответ №1:

Использование C 0x: (смотрите это в прямом эфире наhttp://ideone.com/ZMNb1 )

 #include <type_traits>
#include <iostream>

template <typename T>
    void cannot_take_pointer(T ptr)
{
    static_assert(!std::is_pointer<T>::value, 
        "cannot_take_pointer requires non-pointer argument");
    std::cout << "okn";
}

int main()
{
    int x;
    cannot_take_pointer(x);
    cannot_take_pointer(amp;x);  // fails to compile
}
  

Комментарии:

1. Большое спасибо. Это именно то, что мне было нужно. Хотя у меня еще нет доступа к C 0x, я нашел эквивалентный указатель is_pointer в библиотеке boost.

2. Действительно. Где бы вы ни обнаружили отсутствие частей библиотеки C 11, вы можете смело поспорить, что в boost это есть (некоторые чудеса компилятора в виде макросов, таких как BOOST_AUTO и BOOST_FOREACH). Конечно, также есть BOOST_STATIC_ASSERT

Ответ №2:

На самом деле вам не нужно специализировать ее. Просто добавьте это в тело вашей функции:

 BOOST_STATIC_ASSERT(!boost::is_pointer<T>()::value);
  

Это вызовет сбой, который должно быть довольно легко понять.

Комментарии:

1. как обычно в c , есть несколько хороших ответов :-). Хорошее, простое решение.

2. Да, Джон! Это именно то, что я сделал. Работает как по волшебству. Я с нетерпением жду std::static_assert и возможности предоставить четкую диагностическую строку.

Ответ №3:

звучит как идеальный вариант для boost::disable_if . Что-то вроде этого должно сработать.

 template <class T>
void func(T x, typename boost::disable_if<boost::is_pointer<T> >::type* dummy = 0) {
    std::cout << x << std::endl;
}


func(10); // works
func(std::string("hello")); // works
func("hello world"); // error: no matching function for call to 'func(const char [6])'
func(new int(10)); // error: no matching function for call to 'func(int*amp;)'
  

Комментарии:

1. В C 11, std::enable_if<!std::is_pointer<T>::value> . Кроме того, я бы поместил это в качестве возвращаемого значения или другого аргумента шаблона.

2. Спасибо, Эван. К сожалению, функция, которую я хочу защитить, является operator<< поэтому у меня нет возможности добавить дополнительный аргумент.

3. @Джон Йейтс: Есть другие способы использования disable_if помимо добавления другого параметра. Вам следует ознакомиться с документацией: boost.org/doc/libs/1_47_0/libs/utility/enable_if.html

4. Я прочитал, что disable_if может быть применен либо к новому аргументу, либо к возвращаемому выражению. Подразумевалось, что тип возвращаемого значения функции зависит от параметра шаблона. К сожалению, я реализую пакет с потоковым интерфейсом. Страуструп описывает этот шаблон очень подробно. Важным свойством операторов put (<<) и get(>>) является то, что они возвращают ссылку на поток, а не на зависимый тип. Следовательно, я пришел к выводу, что disable_if не будет работать в моих настройках. Тем не менее, чтение о disable_if помогло мне лучше понять SFINAE.

Ответ №4:

Если вы хотите сделать это самостоятельно (вместо использования чего-то вроде BOOST_STATIC _ASSERT), обычно используются два или три основных приема.

Первое (и, вероятно, самое важное, в вашем случае) — использовать sizeof (обычно с приведением результата к void ), чтобы скомпилировать некоторый код, не создавая ничего, что будет выполняться во время компиляции.

Второй способ заключается в создании некоторого кода, который при определенных обстоятельствах является незаконным. Одним из типичных методов является создание массива, размер которого будет равен значению некоторого выражения. Если выражение имеет значение 0, массив будет иметь размер 0, что недопустимо. В качестве альтернативы, если размер равен единице, это будет допустимо. Единственная проблема с этим заключается в том, что сообщение об ошибке, которое оно выдает, обычно довольно бессмысленно — трудно догадаться, как «ошибка: массив должен иметь положительный размер» (или что-то в этом роде) связано с «параметром шаблона не должен быть указатель».

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

 template <bool>
struct check {  check(...);  };

template <>
class check<false> {};
  

check(...); Означает, что любой другой тип может (теоретически) быть преобразован в check<true> (но обратите внимание, что мы только объявляем функцию, никогда не определяем ее, поэтому, если вы попытаетесь выполнить такой код, он не свяжет). Отсутствие какого-либо конструктора преобразования в check<false> означает, что попытка преобразовать что-либо еще в check<false> всегда завершится неудачей.

Затем мы можем использовать это с макросом, примерно таким:

 #define STATIC_ASSERT(expr, msg) {       
    struct Error_##msg {};               
    (void)sizeof(check<(expr)!=0>((Error_##msg))); 
}
  

Вы бы использовали это что-то вроде: STATIC_ASSERT(whatever, parameter_cannot_be_a_pointer); . Это расширилось бы до чего-то вроде:

 struct Error_parameter_cannot_be_a_pointer {};
(void)sizeof(check<(expr)!=0>(Error_parameter_cannot_be_a_pointer);
  

Затем, если expr != 0, будет предпринята попытка преобразовать Error_parameter_cannot_be_a_pointer в check<true> , которая завершится успешно.

С другой стороны, если expr значение равно 0, будет предпринята попытка преобразования в check<false> , которая завершится неудачей. Мы, по крайней мере, надеемся, что когда это произойдет, мы получим сообщение об ошибке примерно такого вида:

  error cannot convert:
     Error_parameter_cannot_be_a_pointer
 to
     check<false>
  

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

Комментарии:

1. … типичным решением для которой является присвоение массиву отрицательной длины имени, указывающего что-либо, например «int FUNCTION_DOES_NOT_ACCEPT_POINTERS[-1]», в надежде, что имя переменной будет напечатано как часть ошибки компиляции.

2. @JohnZwinck: Это может быть полезно и иногда срабатывает, но, по крайней мере, по моему опыту, компиляторы выводят имена гораздо реже, чем типы.

3. Обратите внимание, что это приведет к серьезной ошибке, если вы попытаетесь использовать одно и то же сообщение об ошибке несколько раз.

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

5. @Jerry: Извините, не увидел фигурные скобки. 🙂