Пересылка не типового аргумента вызывает другое поведение в шаблоне переменных

Кажется, это еще один вопрос «у кого все хорошо?» вопрос, т.к. 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.

Так

  • Какое правильное поведение? (моя ставка для лязга)
  • Как я могу открыть тикет с ошибкой для компилятора, который делает это неправильно?

6

Решение

Существует разумный консенсус в отношении того, что специализации переменных шаблонов могут изменять тип шаблона переменных: 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>(); }
3

Другие решения

Интересно, что в этом примере 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, Может быть, это указатель на проблему? Я не уверен.


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

5

По вопросам рекламы [email protected]