Преобразование указателей на функции-члены в шаблонах - PullRequest
0 голосов
/ 13 сентября 2018

Предположим, у меня есть следующие два класса:

template<typename T>
struct Base
{
    void foo();
};

struct Derived : Base<Derived> {};

Я могу сделать это:

void (Derived::*thing)() = &Derived::foo; 

И компилятор доволен (как я и ожидал).

Когда я помещаю это в два уровня шаблонов, внезапно он взрывается:

template<typename T, T thing>
struct bar {};

template<typename T>
void foo()
{
    bar<void (T::*)(),&T::foo>{};
}

int main()
{
    foo<Derived>();  // ERROR
    foo<Base<Derived>>(); // Works fine
}

Это не с:

non-type template argument of type 'void (Base<Derived>::*)()' cannot be converted to a value of type 'void (Derived::*)()'

godbolt

Почему простой случай работает, а более сложный отказывает? Я считаю, что это связано с этим вопросом, но я не совсем уверен ....

Ответы [ 3 ]

0 голосов
/ 13 сентября 2018

@ YSC прибил тип &Derived::foo;.Поскольку вам интересно, почему это неявное преобразование ...

void (Derived::*thing)() = &Derived::foo; 

... летает нормально, но не в шаблоне, причина заключается в следующем:

[temp.arg.nontype]

2 Аргумент шаблона для нетипичного параметра-шаблона должен быть преобразованным константным выражением типа параметра-шаблона.

[expr.const]

4 Преобразованное константное выражение типа T является выражением, неявно преобразованным в тип T, где преобразованное выражение является константным выражением, а последовательность неявного преобразования содержит только

  • [...]

Список, который я пропустил, не содержит указатель на преобразования членов .Таким образом, делая этот аргумент шаблона недопустимым для указанного вами параметра.


Тривиальным решением было бы использование decltype(&T::foo) вместо void (T::*)() в качестве аргумента типа.Это правильная замена:

bar<decltype(&T::foo), &T::foo>{};

Приемлемо или нет, конечно, зависит от вашего варианта использования, выходящего за рамки MCVE.

0 голосов
/ 13 сентября 2018

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

struct Base {
    void foo();
};

struct Derived : Base {};

Вы по-прежнему не можете сделать foo<Base>(): https://ideone.com/MQE0ff


Возможное альтернативное решение будет включать, во-первых, для упрощения, вместо того, чтобы брать 2 параметра шаблона, которые у меня будут bar использовать тип параметра шаблона auto:

template<auto thing>
struct bar{};

Затем нам нужно реализовать is_specialization_of:

template<template<typename...> class T, typename U>
struct is_specialization_of : std::false_type {};

template<template<typename...> class T, typename... Ts> 
struct is_specialization_of<T, T<Ts...>> : std::true_type {};

Теперь мы можем переписать foo, чтобы использовать is_specialization_of, мы можем определить, передали ли мы специализацию Base или другой класс (которыймы предполагаем, что это происходит из Base специализации.)

template<typename T>
void foo()
{
    conditional_t<is_specialization_of<Base, T>::value, bar<&T::foo>, bar<&Base<T>::foo>>{};
}

Я немного расширил ваш пример, чтобы фактически вызвать thing в bar, и просто включил мои предложения.Вы можете проверить это здесь: https://coliru.stacked -crooked.com / a / 2a33b8bd38896ff5

0 голосов
/ 13 сентября 2018

Это потому, что &Derived::foo на самом деле имеет тип void (Base<Derived>::*)():

[expr.unary]/3

Результатом унарного оператора & являетсяуказатель на его операнд.Операнд должен быть lvalue или квалифицированным идентификатором.Если операндом является квалифицированный идентификатор, называющий нестатический или вариантный член m некоторого класса C с типом T, результат имеет тип «указатель на член класса C типа T» и является предварительным значением, обозначающим C ::m.

Обратите внимание на "член m некоторого класса C с типом T" ... Ужасная формулировка.

...