tmlen
tmlen

Reputation: 9090

Templates arguments not fulfulling all requirements

This is possible:

struct A {
    //void f();  < not declared in struct A
};

template<typename T>
struct Wrapper {
    T t;
    void call_f() { t.f(); }
};

int main() {
    Wrapper<A> w;
}

This compiled fine, as long as w.call_f() is not called. w.call_f() can not be instantiated because A::f does not exist.

I'm having a situation with such a wrapper template that gets used with different T types, which do not always implement all parts of the interface. (Mainly to avoid code duplication).

This does not work:

struct A {
    //using i_type = int;   < not declared/defined
};

template<typename T>
struct Wrapper {
    using its_i_type = typename T::i_type;
       // compile error, even if `i_type` gets never used
};

int main() {
    Wrapper<A> w;
}

neither does this:

struct A {
    //using i_type = int;   < not declared/defined
};

template<typename T>
struct Wrapper {
    typename T::i_type call_f() { return 0; }
       // does not compile, even if `call_f()` is never instantiated
};

int main() {
    Wrapper<A> w;
}

Is there a good way to handle these situations, without a lot of code duplication (like a specialization for Wrapper, etc.)?

Upvotes: 1

Views: 86

Answers (2)

WhiZTiM
WhiZTiM

Reputation: 21576

You can defer the type deduction of its_i_type. Basically, you create a simple wrapper that you must go through.


To extend it to other types you require, (I wanted to suggest type_traits-like solution, but since you don't want specializations) you could define all the types you need:

template<typename T>
struct Wrapper {
private:
    template<typename U> struct i_typper { using type = typename U::i_type; };
    template<typename U> struct k_typper { using type = typename U::k_type; };
    template<typename U> struct p_typper { using type = typename U::p_type; };
public:
    using i_trait = i_typper<T>;
    using k_trait = k_typper<T>;
    using p_trait = p_typper<T>;
};

Example:

struct A { using i_type = int; };
struct B { using i_type = int;    using k_type = float; };

int main() {
    Wrapper<A> w;   //Works now.

    Wrapper<A>::i_trait::type  mk1;  //Works
    Wrapper<A>::k_trait::type  mk2;  //Fails, not defined
    Wrapper<B>::i_trait::type  mk3;  //Works
    Wrapper<B>::k_trait::type  mk4;  //Works
}

For the case of:

template<typename T>
struct Wrapper {
    typename T::i_type call_f() { return 0; }
       // does not compile, even if `call_f()` is never instantiated
};

You have few options here:

  1. make that function a member function template
  2. Use some form of type_traits mechanism, which will still involve specialization
  3. Go the way of abstracting common Wrapper stuff in a base class WrapperBase;

For the first option, you'll have to modify it a bit to further defer deduction

template<typename T>
struct Wrapper {
private:
    template<typename U, typename> struct i_typper { using type = typename U::i_type; };
    template<typename U, typename> struct k_typper { using type = typename U::k_type; };
    template<typename U, typename> struct p_typper { using type = typename U::p_type; };
public:
    using i_trait = i_typper<T, void>;
    using k_trait = k_typper<T, void>;
    using p_trait = p_typper<T, void>;

    template<typename U = void>
    typename k_typper<T, U>::type call_f() { return 0; }
};

I'll leave the second option as an exercise: (it may end up being something like:

template<typename T>
struct wrapper_traits {
    ....
};

template<>
struct wrapper_traits<A>{
   using ....
};

template<typename T>
struct Wrapper {
   ....
public:
    using i_trait = wrapper_traits<T>;
    using k_trait = wrapper_traits<T>;
    using p_trait = wrapper_traits<T>;
};

Jarod's answer is simpler. But this will work if you do not have access to std::experimental, or your company code policy forbids you...

Upvotes: 3

Jarod42
Jarod42

Reputation: 218323

With std::experimental::is_detected, you may do

template<typename T>
using i_type_t = typename T::i_type;

template<typename T>
struct Wrapper {
    using its_i_type = typename std::experimental::detected_t<i_type_t, T>;
       // would be T::i_type or std::experimental::nonesuch
};

Or to better handle case, something like:

template<typename T, bool = std::experimental::is_detected<i_type_t, T>::value>
struct WrapperWithIType {
    // Empty for false case.
};

template<typename T>
struct WrapperWithIType<T, true> {
    using its_i_type = i_type_t<T>;

    its_i_type call_f() { return 0; }
};

and then

template<typename T>
struct Wrapper : WrapperWithIType<T> {
    // Common stuff
};

Upvotes: 2

Related Questions