Преобразование указателя-к-члену базы в указатель-к-члену

Упрощенный пример из недавнего сообщения в блоге:

struct B { void f(); };
struct D : B { };

constexpr auto as_d = static_cast<void(D::*)()>(&D::f); // (1)

template <void (D::*)()>
struct X { };

X<as_d> x; // (2)

gcc, clang и MSVC все принимают объявление as_d помеченного (1). gcc и clang оба отклоняют объявление x помечено (2), но MSVC принимает его.

Оба сообщения gcc и clang указывают, что они знают, что as_d является указателем на элемент B лязг:

<source>:9:3: ошибка: извините, аргумент шаблона типа не указателя типа void (D::*)() который относится к члену B::f другого класса, еще не поддерживается

НКА:

<source>:9:7: error: void (D::*)(){((void (D::*)())B::f), 0} не является допустимым аргументом шаблона для типа void (D::*)()

Кто прав? Если gcc/clang, каково правило, от которого мы справляемся? Кажется, что as_d является преобразованным постоянным выражением типа void (D::*)() для меня...

Ответ 1

Ну, это оказалось довольно интересным. Что касается языка, программа действительна - as_d соответствует требованию быть допустимым аргументом шаблона не-типа (это преобразованное константное выражение правильного типа).

Тем не менее, ABI Itanium C++ ABI, по- видимому , не указывает на искажение для этой ситуации (то есть имеет аргумент шаблона не-типа, тип которого является указателем-на-член-к-производному, но значение которого представляет собой указатель- член к основанию). Компиляторы, нацеленные на то, что ABI (т.е. clang и gcc), в результате, не могут принять этот код. Это объясняет, почему ошибка clang "извините, еще не", а не "нет, плохо!"

С другой стороны, другие ABI не имеют такой проблемы с манипуляцией, и поэтому MSVC и ICC могут скомпилировать программу просто отлично.