Может использоваться статическая константа constexpr как аргумент шаблона

У меня есть следующий фрагмент кода, который компилируется на более раннем gcc, но не на версии 6 (работает с -std = С++ 1z). Кланг тоже отвергает это, заявляя, что объект val не имеет правильной связи. Я не понимаю разницы. Не является ли константная переменная типа указателя, который должен работать более или менее прозрачно? Есть ли что-то, что мне не хватает в синтаксисе, который позволит это работать? Или это нарушает некоторую часть стандарта?

typedef void(*t_voidfn)();
template <t_voidfn> struct s {};
void fn() {
  static constexpr t_voidfn val = &fn;
  s<val> x;
}

С другой стороны, это работает.

typedef void(*t_voidfn)();
template <t_voidfn> struct s {};
void fn() {
  s<&fn> x;
}

Ответ 1

Первый фрагмент верен в С++ 17, но не в С++ 14 и 11.

Для С++ 14, [temp.arg.nontype]/1 говорит:

Аргумент шаблона для непигового, не-шаблона template-parameter должен быть одним из:

[...]

  • постоянное выражение (5.19), которое обозначает адрес полного объекта со статическим временем хранения и внешним или внутренним связь или функция с внешней или внутренней связью, включая шаблонов функций и шаблонов-шаблонов функций, но исключая нестатические члены класса, выраженные (игнорируя круглые скобки) как &id-expression, где id-выражение - это имя объекта или функции, за исключением того, что & может быть опущено, если имя ссылается на функции или массива и должны быть опущены, если соответствующие шаблон-параметр является ссылкой; или
  • константное выражение, которое вычисляет значение нулевого указателя (4.10); или
  • константное выражение, которое вычисляет значение указателя нулевого элемента (4.11); или
  • указатель на элемент, выраженный как описано в 5.3.1; или
  • постоянное выражение типа std::nullptr_t.

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

В принципе, адрес функции в вашем примере должен быть выражен строго как &fn или fn.

С++ 11 содержит по существу одну и ту же формулировку минус несколько разъяснений, введенных отчетами о дефектах между 11 и 14:

  • DR1570 уточнил бит о полных объектах;
  • DR1398 с поправкой DR1666 добавила последнюю пулю.

Для С++ 17 ограничения были смягчены в результате принятия бумаги N4268 (обоснование в N4198). Соответствующий пункт (2) теперь говорит:

Аргумент шаблона для параметра шаблона, не относящегося к типу, должен быть преобразованное постоянное выражение (5.20) типа Шаблон-параметры. Для шаблона-шаблона, не относящегося к типу ссылки или тип указателя, значение константного выражения не должно ссылаться на (или для типа указателя, не должен быть адресом):

  • подобъект (1.8),
  • временный объект (12.2),
  • строковый литерал (2.13.5),
  • результат выражения typeid (5.2.8) или
  • предопределенная переменная __func__ (8.4.1).

[Примечание. Если аргумент template представляет собой набор перегруженных функции (или указатель или указатель элемента к такому), соответствие функция выбрана из набора (13.4). - конечная нота]

N4198 содержит хорошие пояснения для каждой из этих пуль.