C++: use SFINAE to detect argument types in Visitor

132 Views Asked by At

I am writing a function that takes a visitor of the form:

struct Visitor {
    void operator()(int i) {...}
    void operator()(const std::string& s)  {...}
    void operator()(const auto& s)  {...}
};

When my value being visited is a non-const string, I want to detect the const-ness of the visitor function arg. eg if someone passes this visitor:

struct Visitor {
    void operator()(const std::string& s)  {...}
    void operator()(const auto& s)  {...}
};

or

struct Visitor {
    void operator()(const auto& s)  {...}
};

I would like to detect that they will definitely receive a const ref, whereas if they passed:

struct Visitor {
    void operator()(std::string& s)  {...}
    void operator()(auto& s)  {...}
};

or

struct Visitor {
    void operator()(const std::string& s)  {...}
    void operator()(auto& s)  {...}
};

I want to detect that they will be receiving a non-const ref.

I am trying to do this with templates by relying on ambiguous overload errors to detect if the struct contains operator() with the particular arg type. For example I can do this (no templates):

struct TestConstStringArg {
    void operator()(const std::string& str) {}
};

struct Tester : public TestConstStringArg, public Visitor {
    void tester() {
        std::string s;
        (*this)(s);
    }
};

This will give a compile error if Visitor also has a operator(const std::string&) member, since it will be an ambiguous function resolution.

How can I leverage this in a template to give a constexpr bool that I can check? Currently I have this:

template <class T, class ArgTest>
struct ArgTester : public T, public ArgTest {
    void tester() {
        std::string s;
        (*this)(s);
    }
};

template<class T>
struct VisitorArgTester {
    typedef char yes[1];
    typedef char no[2];

    template<class ArgTest> static no& test(ArgTester<T, ArgTest>* t);
    template<class ArgTest> static yes& test(...);

    static constexpr bool hasConstStr = sizeof(test<TestConstStringArg>(nullptr)) == sizeof(yes);
};


struct Visitor {
    void operator()(const std::string& str) {}
};
static_assert(VisitorArgTester<Visitor>::hasConstStr);

The idea here is that the version of test() returning no& would compile ok if T did not contain the const string ref arg version, otherwise the ambiguous error would cause the yes& version to be used instead. But this does not seem to work, the static_assert is always failing whether or not Visitor has the operator. What have I done wrong?

1

There are 1 best solutions below

0
Larry On

TBH, I'm a bit unclear about what you need exactly. The situation also runs deeper than it first appears (longer story). Rather than dissecting your own solution (I know it's what you asked), for now I'm just posting a quick solution of my own at the top of my head that you might be able to use as-is or possibly leverage for your needs (imperfect though still - "static_cast" seen in the solution won't distinguish between noexcept members for instance though that can be handled using more elaborate means if required). I'm not sure if it's even what you're after but first attempt to gauge the situation.

Click here to run it.

#include <type_traits>
#include <iostream>

template <typename C, typename F>
using FuncToMemberFuncPtr_t = F C::*;

template <typename, typename, typename = void>
struct HasOperator: std::false_type
{
};

template <typename C, typename F>
struct HasOperator<C,
                   F,
                   std::void_t<decltype(static_cast<FuncToMemberFuncPtr_t<C, F>>(&C::operator()))>
                  > : std::true_type
{
};

template <typename C, typename F>
inline constexpr bool HasOperator_v = HasOperator<C, F>::value;

template <typename C>
inline constexpr bool HasOperatorWithNonConstStringRef_v = HasOperator_v<C, void (std::string &) > ||
                                                           HasOperator_v<C, void (std::string &) const>;


template <typename C>
inline constexpr bool HasOperatorWithConstStringRef_v = HasOperator_v<C, void (const std::string &) > ||
                                                        HasOperator_v<C, void (const std::string &) const>;
struct Visitor1
{
    void operator()(int i);
    void operator()(std::string &);
};

struct Visitor2
{
    void operator()(int i);
    void operator()(const std::string &);
};

int main()
{
    std::cout << "Visitor1 -> void (std::string &): " << std::boolalpha << HasOperatorWithNonConstStringRef_v<Visitor1> << '\n'; // true
    std::cout << "Visitor1 -> void (const std::string &): " << std::boolalpha << HasOperatorWithConstStringRef_v<Visitor1> << '\n'; // false
    std::cout << "Visitor2 -> void (std::string &): " << std::boolalpha << HasOperatorWithNonConstStringRef_v<Visitor2> << '\n'; // false
    std::cout << "Visitor2 -> void (const std::string &): " << std::boolalpha << HasOperatorWithConstStringRef_v<Visitor2> << '\n'; // true

    return 0;
}