Использование SFINAE для обнаружения метода с помощью GCC - PullRequest
3 голосов
/ 04 августа 2020

У меня возникли проблемы с использованием концепции SFINAE, чтобы определить, имеет ли класс определенный метод шаблона с некоторыми компиляторами, наиболее подходящим для G CC. Рассмотрим код ниже. Он компилируется и работает с MSVC, как и ожидалось; однако, когда я компилирую тот же код с G CC, он жалуется, что std::vector<double> не имеет метода сериализации. Конечно, тот факт, что метода не существует, верен, но я ожидал, что это приведет к сбою подстановки и компилятору, который определит, что другой, менее специализированный резервный вариант является наиболее подходящим. Мне что-то не хватает в SFINAE или это ошибка G CC?

#include <iostream>
#include <utility>
#include <vector>

class SerializerBase
{
  public:
    SerializerBase() {}
};

template<typename T>
struct has_serialize
{
    template<typename C>
    static constexpr auto test(...) -> std::false_type;
    /// Note: this is where the compiler complains about the .serialize method
    /// during substitution.
    template<typename C>
    static constexpr auto test(int)
      -> decltype(std::declval<T>().serialize(std::declval<SerializerBase&>()), std::true_type());

    using result_type       = decltype(test<T>(0));
    static const bool value = result_type::value;
};

class Serializer : public SerializerBase
{
  public:
    Serializer() {}

    template<typename T,
             typename std::enable_if<!has_serialize<T>::value, T>::type* = nullptr>
    void operator()(const T& v)
    {
        std::cout << "fallback called" << std::endl;
    }

    template<typename T,
             typename std::enable_if<has_serialize<T>::value, T>::type* = nullptr>
    void operator()(const T& v)
    {
        v.serialize(*this);
    }
};

struct Foo
{
    template<typename SerializerType>
    void serialize(SerializerType& s) const
    {
        std::cout << "serialize called" << std::endl;
    }
};

int main()
{
    Serializer s;
    std::vector<double> v;
    Foo f;
    s(v);
    s(f);

    return 0;
}

1 Ответ

4 голосов
/ 04 августа 2020

В случае, если кто-то еще сталкивается с подобной ошибкой / недоразумением, моя ошибка (точно указанная n. 'Местоимениями' m.) Использовала неправильный тип в has_serialize::test. Из того, что я могу сделать (по своей наивности), это то, что для

template<typename T>
struct has_serialize
{
    // ...
    template<typename C>
    static constexpr auto test(int)
      -> decltype(std::declval<T>().serialize(std::declval<SerializerBase&>()), std::true_type());
    // ...
};

шаблон C не появляется в has_serialize::test, и в результате G CC не предоставляет возможности для замены неудача. Следовательно, G CC пытается оценить std::declval<T>().serialize(...) и, очевидно, выдает ошибку, когда T = std::vector<double>. Если T заменяется на C в этой строке, то компилятор распознает это как ошибку подстановки и определяет подпись как неподходящую.

Более того, как указал Максим Егорушкин, существует вероятность того, что T::serialize может возвращать определяемый пользователем тип, который перегружает operator,. Чтобы уменьшить (хотя и очень маловероятно) потенциальную ошибку, код должен быть:

template<typename T>
struct has_serialize
{
    // ...
    template<typename C>
    static constexpr auto test(int)
      -> decltype(static_cast<void>(std::declval<C>().serialize(std::declval<SerializerBase&>())), std::true_type());
    // ...
};
...