SFINAE Duplicate constructor declaration

2019-05-29 08:00发布

问题:

I want to create constructors for a class in a way that the compiler trivially create new instances of it when needed.

Here's an example.

class C {
    public:
    C(int) {}; // int constructor
};

If I then declare a function:

void F(C _c) {};

I can call this one with an int and have the construction of C handled by the compiler:

F(0); // works

What I want to do is to achieve the same thing, but with lambdas as parameters, a few examples:

F([]() {});                     //A
F([](int) {});                  //B
F([](int)->int { return 0; });  //C

With SFINAE and from what I've learned from another question: Auto-constructor not working with <functional> objects

I managed to sort out a way to create a constructor that matches only a specific lambda signature, it would work out like this:

template<typename F, typename = decltype(function<void(void)>(declval<F&>()))> C(F&& f) {}; //For lambda's like A
template<typename F, typename = decltype(function<void(int)>(declval<F&>()))> C(F&& f) {};  //For lamdba's like B
template<typename F, typename = decltype(function<int(int)>(declval<F&>()))> C(F&& f) {};   //For lambda's like C

Now the problem that I have is that if I add these three definitions at once, I get an error stating that I'm trying to redefine the constructor of C. This is correct because, yeah, the constructor's being defined as C(F&& f) three times, however, how should I let know the compiler to use a different signature for each different case?

The other answer hinted me to look at enable_if and is_convertible but haven't managed to set up a workaround for this issue. Any help is greatly appreciated.

Using: Apple LLVM version 6.0 (clang-600.0.57) (based on LLVM 3.5svn)

回答1:

Your current problem is that you define 3 times

template <typename F, typename> C(F&&);

but with different default argument (to do SFINAE).

You may change to

// Use to have different type easily
template <std::size_t> struct dummy {}

template<typename F, decltype(function<void(void)>(declval<F&>()), dummy<0>())* = nullptr> C(F&& f);
template<typename F, decltype(function<void(int)>(declval<F&>()), dummy<1>())* = nullptr> C(F&& f);
template<typename F, decltype(function<int(int)>(declval<F&>()), dummy<2>())* = nullptr> C(F&& f);

So you have 3 different signatures:

template<typename F, dummy<0>*> C(F&& f);
template<typename F, dummy<1>*> C(F&& f);
template<typename F, dummy<2>*> C(F&& f);


回答2:

Based on top answer here, this is what I came up with. It is probably a little crude and can be improved on but the main thing it works (tried on online clang 3.5.0).

template <typename T>
struct function_traits
    : public function_traits<decltype(&T::operator())>
{};

template <typename ClassType, typename ReturnType, typename... Args>
struct function_traits<ReturnType(ClassType::*)(Args...) const>
{
    typedef ReturnType(signature)(Args... args);
    typedef ReturnType(*ptr_signature)(Args... args);
};


class C
{
public:
    template<typename F> C(F&& f) {
        typedef function_traits<F> traits;
        I<F>(std::move(f), reinterpret_cast<typename traits::ptr_signature>(0));
    }

    template<typename F> void I(F&& f, void(*)()) { std::cout << "Hello void(void) lambda" << std::endl; };
    template<typename F> void I(F&& f, void(*)(int)) { std::cout << "Hello void(int) lambda" << std::endl; };
    template<typename F> void I(F&& f, int(*)(int)) { std::cout << "Hello int(int) lambda" << std::endl; };
};

int main()
{
    C([](int i) { return i;});
    C([](int i) {});
    C([]() {});
}