Jaeya
Jaeya

Reputation: 141

SFINAE and number of parameters

I have a template class C<T> which I intend to instantiate with T as some other classes A and B. C<T> has a method foo whose signature I would like to depend on whether T was instantiated as A or B. For example, consider the following code:

#include <iostream>
#include <string>

class A {
public:
    void message() {
        std::cout << "message with no args" << std::endl;
    }
};

class B {
public:
    void message(int x) {
        std::cout << "message with " << x << std::endl;
    }
};

template<typename T>
class C {
private:
    T internal;
public:
    C(T& x) {
        internal = x;
    }
    void call() {
        internal.message();
    }
    void call(int x) {
        internal.message(x);
    }
};

int main(int argc, char* argv[]) {
    A a;
    B b;
    C<A> ca(a);
    C<B> cb(b);
    ca.call();
    cb.call(42);
//  ca.call(42);  ERROR HERE
    return 0;
}

This runs correctly. ca.call(42) would raise a compilation error because there is no method A::message(int). However, if I for some reason introduce a method A::message(int) in A, the code may allow calling ca.call(42), which I would like to prevent.

I know that SFINAE techniques would allow to declare a method C::call(T::call_type x) where T::call_type would be a typedef for each intended instantiation of T. However, this only allows me to change the type of the argument of C::call. I would like instead to make the signature (in particular, the number of parameters) of C::call on T. I would thus prevent ca.call(42) from being a valid call even if there is a method A::message(int) in A.

Is there any way to do this?

Upvotes: 1

Views: 263

Answers (2)

Humphrey Winnebago
Humphrey Winnebago

Reputation: 1702

I don't know all the ins and outs of SFINAE, but what do you think of this?

template <
    typename = std::enable_if_t<std::is_same<std::decay_t<T>, A>::value>>
void call() {
    internal.message();
}
template <
    typename = std::enable_if_t<std::is_same<std::decay_t<T>, B>::value>>
void call(int x) {
    internal.message(x);
}

You can use == false too

template <
    typename = std::enable_if_t<std::is_same<std::decay_t<T>, B>::value == false>>

Upvotes: 1

Rakete1111
Rakete1111

Reputation: 48998

You can do this with template specializations:

// Main template used by every other type T.
template<typename T>
class C; // or some other implementation.

// This gets used for T = A.
template<>
class C<A> {
private:
    A internal;
public:
    C(A& x) {
        internal = x;
    }
    void call() {
        internal.message();
    }
};

// This gets used for T = B.
template<>
class C<B> {
private:
    B internal;
public:
    C(B& x) {
        internal = x;
    }
    void call(int x) {
        internal.message(x);
    }
};

If you don't like that you need to duplicate some common code, then you can have a base class with all those common things and inherit from it in each specialization.

Upvotes: 0

Related Questions