1

Trying to specialize member methods.
Reading this previous question: std::enable_if to conditionally compile a member function
I can quite understand what I am doing wrong.

#include <string>
#include <iostream>
#include <type_traits>

template<typename T>
class Traits
{
};

struct Printer
{
    template<typename T>
    typename std::enable_if<!std::is_function<decltype(Traits<T>::converter)>::value, void>::type
    operator()(T const& object)
    {
        std::cout << object;
    }
    template<typename T>
    typename std::enable_if<std::is_function<decltype(Traits<T>::converter)>::value, void>::type
    operator()(T const& object)
    {
        std::cout << Traits<T>::converter(object);
    }
};

template<>
class Traits<std::string>
{
    public:
        static std::size_t converter(std::string const& object)
        {
            return object.size();
        }
};

int main()
{
    using namespace std::string_literals;

    Printer     p;

    p(5);
    p("This is a C-string");
    p("This is a C++String"s);  // This compiles.
}

Compilation Gives:

> g++ -std=c++1z X.cpp
X.cpp:42:5: error: no matching function for call to object of type 'Printer'
    p(5);
    ^
X.cpp:14:5: note: candidate template ignored: substitution failure [with T = int]: no member named 'converter' in 'Traits<int>'
    operator()(T const& object)
    ^
X.cpp:20:5: note: candidate template ignored: substitution failure [with T = int]: no member named 'converter' in 'Traits<int>'
    operator()(T const& object)
    ^

They both seem to fail because they can't see the method converter. But I am trying to use SFINE and std::enable_if to recognize that this function does not exist and thus only instantiate one of the methods.

The same error is generated for each of the types:

X.cpp:43:5: error: no matching function for call to object of type 'Printer'
    p("This is a C-string");
    ^
X.cpp:14:5: note: candidate template ignored: substitution failure [with T = char [19]]: no member named 'converter' in 'Traits<char [19]>'
    operator()(T const& object)
    ^
X.cpp:20:5: note: candidate template ignored: substitution failure [with T = char [19]]: no member named 'converter' in 'Traits<char [19]>'
    operator()(T const& object)
    ^

Note: It compiles for the std::string version.

Martin York
  • 257,169
  • 86
  • 333
  • 562

3 Answers3

2

You could defer to a private helper function, and use overload resolution to prefer to the positively SFINAE-d overload - and not have a negatively SFINAE-d one:

struct Printer
{
    template <class T>
    void operator()(T const& object) {
        call_impl(object, 0);
    }

private:
    // selected if Traits<T>::converter exists and is a function
    // preferred in this case because int is better than ...
    template<typename T>
    typename std::enable_if<std::is_function<decltype(Traits<T>::converter)>::value, void>::type
    call_impl(T const& object, int)
    {
        std::cout << Traits<T>::converter(object);
    }

    // selected if either Traits<T>::converter doesn't exist or isn't a function
    template<typename T>
    void call_impl(T const& object, ...)
    {
        std::cout << object;
    }

};

One of the nice benefits that we'll get in C++2a with constraining functions is that we can do this without the extra helper:

struct Printer
{
    template <class T>
        requires std::is_function<decltype(Traits<T>::converter)>::value
    void operator()(T const& object)
    {
        std::cout << Traits<T>::converter(object);
    }

    template <class T>
    void operator()(T const& object)
    {
        std::cout << object;
    }
};
Barry
  • 286,269
  • 29
  • 621
  • 977
0

How about adding non-function converter to non-specialized trait?

template<typename T>
class Traits
{
    public: enum class Dummy{nothing};
    public: static Dummy const converter = Dummy::nothing;
};

Run this code online

user7860670
  • 35,849
  • 4
  • 58
  • 84
0

The problem is in how SFINAE works. When substitution fails, the entire function is taken out of the program. So even though your predicate typename std::enable_if<!std::is_function<decltype(Traits<T>::converter)>::value, void>::type is meant to catch the false case, the non-existence of converter will cause the overload to be taken off the table.

The easiest workaround is something like this:

struct Printer
{
    template<typename T>
    void
    impl(T const& object, ...)
    {
        std::cout << object;
    }

    template<typename T>
    typename std::enable_if<std::is_function<decltype(Traits<T>::converter)>::value, void>::type
    impl(T const& object, void*)
    {
        std::cout << Traits<T>::converter(object);
    }

    template<typename T>
    void
    operator()(T const& x)
    {
        return impl(x, nullptr);
    }
};

Basically: You give the compiler something that will always work without using the predicate. The trick here is that nullptr will be matched to void* instead of ..., so it will do what you want.

If you want to get real fun about it, you can make a has_converter function whose return type is true_type or false_type and overload the implementation on that.

struct Printer
{
    template<typename T>
    std::false_type
    has_converter(T const& object, ...);

    template<typename T>
    typename std::enable_if<std::is_function<decltype(Traits<T>::converter)>::value, std::true_type>::type
    has_converter(T const& object, void*);

    template<typename T>
    void impl(T const& x, std::false_type)
    {
        std::cout << x;
    }

    template<typename T>
    void impl(T const& x, std::true_type)
    {
        std::cout << Traits<T>::converter(x);
    }

    template<typename T>
    void
    operator()(T const& x)
    {
        return impl(x, decltype(has_converter(x, nullptr))());
    }
};

One can imagine a helper function or templated constexpr bool to make using this property even easier (use the same technique as above).

template <typename T>
constexpr bool has_converter = ???;
Travis Gockel
  • 26,877
  • 14
  • 89
  • 116