Кажется, это еще один вопрос «у кого все хорошо?» вопрос, т.к. gcc 6.0.0 и clang 3.7.0 ведут себя по-разному.
Давайте предположим, что у нас есть шаблон переменной, который принимает const char *
как не шаблонный аргумент и специализируется для данного указателя:
constexpr char INSTANCE_NAME[]{"FOO"};
struct Struct{ void function() const { std::cout << __PRETTY_FUNCTION__; } };
std::ostream &operator <<(std::ostream &o, const Struct &) { return o << INSTANCE_NAME; }
template <const char *> char Value[]{"UNKNOWN"};
// spezialization when the pointer is INSTANCE_NAME
template < > Struct Value<INSTANCE_NAME>{};
Обратите внимание, что переменные шаблона имеют разные типы в зависимости от специализации. Десять у нас есть две функции шаблона, каждая из которых занимает const char *
как не шаблонный аргумент и вперед это к шаблону переменной:
template <const char *NAME> void print()
{
std::cout << Value<NAME> << '\n';
}
template <const char *NAME> void call_function()
{
Value<NAME>.function();
}
Затем вызов этой функции приводит к другому поведению:
int main()
{
print<INSTANCE_NAME>();
call_function<INSTANCE_NAME>();
return 0;
}
clang 3.7.0 отпечатков FOO
а также void Struct::function() const
(как я и ожидал), в то время как gcc 6.0.0 не скомпилируется с ошибкой ниже:
запрос на член ‘function’ в ‘Value’, который имеет неклассовый тип ‘char [8]’
Я почти уверен, что GCC не удалось вперед шаблон не тип аргумента NAME
в шаблон переменной Value
в функции call_function
и по этой причине он выбирает шаблон неспециализированной переменной, который имеет 'char [8]'
тип…
Он действует как копирование аргумента шаблона. Это происходит только при вызове функции-члена объекта, если мы комментируем тело call_function
, выход FOO
не UNKNOWN
так что в print
функционировать пересылка работает даже в gcc.
Так
Существует разумный консенсус в отношении того, что специализации переменных шаблонов могут изменять тип шаблона переменных: C ++ 1y / C ++ 14: специализация переменных шаблонов?
Поведение gcc особенно интересно, если тип по умолчанию Value
изменяется на тип с function
метод:
struct Unknown{ void function() const { std::cout << __PRETTY_FUNCTION__; } };
template <const char *> Unknown Value;
prog.cc: In instantiation of 'void call_function() [with const char* NAME = ((const char*)(& INSTANCE_NAME))]':
prog.cc:26:18: required from here
prog.cc:20:5: error: 'Unknown::function() const' is not a member of 'Struct'
Value<NAME>.function();
^
Ошибка, по-видимому, заключается в том, что, когда шаблон неспециализированной переменной имеет тип, который не зависит от параметров шаблона шаблона переменной, gcc предполагает, что в методах шаблона, использующих этот шаблон переменной, шаблон переменной всегда имеет этот тип.
Обходной путь, как обычно, состоит в том, чтобы безоговорочно переслать шаблон переменной в шаблон класса со специализацией (спецификациями) шаблона класса и с необходимыми изменениями для соответствия требованиям ODR.
Другой (возможно, более простой) обходной путь заключается в том, чтобы сделать неспецифический тип шаблона переменной как-то зависимым от параметров шаблона шаблона переменной; в вашем случае это будет работать:
template <const char *P> decltype(*P) Value[]{"UNKNOWN"};
Я не могу найти соответствующую проблему в gcc bugzilla так что вы можете захотеть ввести новый. Вот минимальный пример:
struct U { void f() {} };
struct V { void f() {} };
template<class T> U t;
template<> V t<int>;
template<class T> void g() { t<T>.f(); }
int main() { g<int>(); }
Интересно, что в этом примере GCC даже противоречив.
Давайте объявим неполный шаблонный класс, который должен дать несколько хороших сообщений компилятора, которыми мы можем злоупотреблять:
template <typename T>
struct type_check;
Мы также сделаем еще const char*
что мы можем использовать для тестирования:
constexpr char NOT_FOO[]{"NOT_FOO"};
Теперь посмотрим, что запустит компилятор:
template <const char *NAME> void foo()
{
type_check<decltype(Value<FOO>)> a;
type_check<decltype(Value<NAME>)> b;
type_check<decltype(Value<NOT_FOO>)> c;
type_check<decltype(Value<FOO>.foo())> d;
type_check<decltype(Value<NAME>.foo())> e;
type_check<decltype(Value<NOT_FOO>.foo())> f;
}
Вот ошибки, которые выдает GCC 5.1.0 (отредактировано для ясности):
test.cpp:21:38: error: ‘type_check<Foo> a’ has incomplete type
type_check<decltype(Value<FOO>)> a;
^
test.cpp:22:39: error: ‘type_check<Foo> b’ has incomplete type
type_check<decltype(Value<NAME>)> b;
test.cpp:25:42: error: ‘type_check<char [8]> c’ has incomplete type
type_check<decltype(Value<NOT_FOO>)> c;
^
test.cpp:23:44: error: ‘type_check<void> c’ has incomplete type
type_check<decltype(Value<FOO>.foo())> c;
test.cpp:24:37: error: request for member ‘foo’ in ‘Value<NAME>’, which is of non-class type ‘char [8]’
type_check<decltype(Value<NAME>.foo())> d;
test.cpp:28:40: error: request for member ‘foo’ in ‘Value<((const char*)(& NOT_FOO))>’, which is of non-class type ‘char [8]’
type_check<decltype(Value<NOT_FOO>.foo())> f;
Давайте возьмем их по одному.
Ошибка 1:
test.cpp:21:38: error: ‘type_check<Foo> a’ has incomplete type
type_check<decltype(Value<FOO>)> a;
В первой ошибке мы видим, что GCC правильно выводит, что тип Value<FOO>
является Foo
, Это то, что мы ожидаем.
Ошибка 2:
test.cpp:22:39: error: ‘type_check<Foo> b’ has incomplete type
type_check<decltype(Value<NAME>)> b;
Здесь GCC правильно выполняет пересылку и решает, что Value<NAME>
имеет тип Foo
,
Ошибка 3:
test.cpp:25:42: error: ‘type_check<char [8]> c’ has incomplete type
type_check<decltype(Value<NOT_FOO>)> c;
Большой, Value<NOT_FOO>
является "UNKNOWN"
так что это правильно.
Ошибка 4:
test.cpp:23:44: error: ‘type_check<void> c’ has incomplete type
type_check<decltype(Value<FOO>.foo())> c;
Это отлично, Value<FOO>
является Foo
, который мы можем назвать foo
на возвращение void
,
Ошибка 5:
test.cpp:24:37: error: request for member ‘foo’ in ‘Value<NAME>’, which is of non-class type ‘char [8]’
type_check<decltype(Value<NAME>.foo())> d;
Это странный. Хотя в ошибке 2 мы видим, что GCC знает, что тип Value<NAME>
является Foo
, когда он пытается сделать поиск для foo
функция, она получает это неправильно и использует вместо этого основной шаблон. Это может быть некоторой ошибкой в поиске функции, которая не правильно разрешает значения аргументов шаблонного типа.
Ошибка 6:
test.cpp:28:40: error: request for member ‘foo’ in ‘Value<((const char*)(& NOT_FOO))>’, which is of non-class type ‘char [8]’
type_check<decltype(Value<NOT_FOO>.foo())> f;
Здесь мы видим, как компилятор правильно выбирает основной шаблон при разработке Value<NOT_FOO>
является. Меня интересует то, что (const char*)(& NOT_FOO))
который GCC выводит как тип NOT_FOO
, Может быть, это указатель на проблему? Я не уверен.
Я бы предложил подать ошибку и указать на несоответствие. Может быть, это не полностью отвечает на ваш вопрос, но я надеюсь, что это поможет.