Я следил за сообщением в блоге Жана Гегана SFINAE, где он реализует свойство типа с помощью sizeof () , которое проверяет, есть ли в классе функция сериализации . Внутри у него есть структура действительноХас , которая проверяет, является ли сериализация просто членом класса или фактической функцией-членом, выполнив следующие действия.

template<typename T>
struct has_serialize {
    typedef char yes[1];
    typedef char no[2];

    template<typename U, U u> struct reallyHas{}; 

    template <typename C>
    static yes& test(reallyHas<string (C::*)(), &C::serialize>*){}

    // for const method
    template<typename C>
    static yes& test(reallyHas<string (C::*)() const, &C::serialize>*){}

    template<typename> // variadic template
    static no& test(...){} // sink hole

    enum {
         value = (sizeof(test<T>(0)) == sizeof(yes))
    };
};

Это работает нормально только тогда, когда тестовая функция имеет подпись

template <typename C>
static yes& test(reallyHas<string (C::*)(), &C::serialize>*){}

Но не когда

template <typename C>
static yes& test(reallyHas<string (C::*)(), &C::serialize>){}

Я действительно не понимаю значения * в аргументе. В конце концов, мы просто проверяем, происходит ли подстановка или нет, поэтому она не должна работать в обоих случаях

2
Bad_Panda 20 Май 2021 в 14:19

1 ответ

Лучший ответ

В упрощенном виде принцип SFINAE состоит в том, чтобы предоставить несколько кандидатов для символа (перегрузки, специализации шаблонов и т. Д.). Затем для данного аргумента компилятор будет проверять каждого кандидата по одному, пока не найдет наилучшее совпадение.

В этом случае функция test используется для SFINAE. Цель состоит в том, чтобы определить его таким образом, чтобы для типов с функцией-членом serialize он преобразовывался в функцию test, которая возвращает тип yes, а для функции, возвращающей { В противном случае введите {X4}}.

В приведенном выше примере это достигается с помощью трюка, при котором 0 преобразуется в указатель. Такое преобразование возможно по правилам языка.

Когда вызывается test<T>(0), компилятор сначала проверяет первую перегрузку и, подставив T, получит функцию, которая выглядит примерно так:

static yes& test(reallyHas<string (T::*)(), &T::serialize>*){}

Теперь компилятор увидит, можно ли выбрать эту перегрузку. Он проверит, можно ли преобразовать 0 (int) в reallyHas<string (T::*)(), &T::serialize>*. Поскольку int могут быть преобразованы в указатели, ошибка возникнет только в том случае, если тип reallyHas<string (T::*)(), &T::serialize>* будет неправильно сформирован. Это тот случай, если T не имеет подходящей функции-члена &T::serialize, которую можно преобразовать в указатель функции string (T::*)(). Например, для using T = int он потерпит неудачу, поскольку int даже не является типом класса.

Если замена не удалась, компилятор аналогичным образом попробует вторую перегрузку и, наконец, третью. Поскольку третий объявлен как static no& test(...){}, он может принимать любые аргументы. В этом случае он может быть объявлен как static no& test(unsigned){}, так как 0 может быть преобразован в unsigned.

Теперь обратите внимание, что произойдет, если вы измените первую перегрузку на это:

template <typename C>
static yes& test(reallyHas<string (C::*)(), &C::serialize>){}

Теперь ошибка замены будет происходить всегда (даже если T имеет функцию-член serialize), поскольку 0 (int) не может быть преобразован в тип класса reallyHas<T> .

2
janekb04 20 Май 2021 в 13:19