Home > front end >  How can I get the type of underlying data in a SFINAE template definition?
How can I get the type of underlying data in a SFINAE template definition?

Time:11-10

Say I have a library function int foo( const T& ) that can operate with some specific containers as argument:

std::vector<A> c1;
std::list<B>() c2;
auto a1 = foo(c1);  // ok
auto a2 = foo(c2);  // ok too

std::map<int, float> c3;
auto a3 = foo( c3 ); // this must fail 

First, I wrote a traits class defining the allowed containers:

template <typename T>               struct IsContainer                    : std::false_type { };
template <typename T,std::size_t N> struct IsContainer<std::array<T,N>>   : std::true_type  { };
template <typename... Ts>           struct IsContainer<std::vector<Ts...>>: std::true_type  { };
template <typename... Ts>           struct IsContainer<std::list<Ts...  >>: std::true_type  { };

now, "Sfinae" the function:

template<
    typename U,
    typename std::enable_if<IsContainer<U>::value, U>::type* = nullptr
>
auto foo( const U& data )
{
 // ... call some other function
    return bar(data);
}

Works fine.

side note: bar() is actually inside a private namespace and is also called from other code. On the contrary, foo() is part of the API.

But now I want to change the behavior depending on the types inside the container i.e. inside foo():

  • call bar1(data) if argument is std::vector<A> or std::list<A> and

  • call bar2(data) if argument is std::vector<B> or std::list<B>

So I have this:

struct A {}; // dummy here, but concrete type in my case
struct B {};

template<
    typename U,
    typename std::enable_if<
        ( IsContainer<U>::value, U>::type* = nullptr && std::is_same<U::value_type,A> )
    >
auto foo( const U& data )
{
 // ... call some other function
    return bar1(data);
}

template<
    typename U,
    typename std::enable_if<
        ( IsContainer<U>::value, U>::type* = nullptr && std::is_same<U::value_type,B> )
    >
auto foo( const U& data )
{
 // ... call some other function
    return bar2(data);
}

With bar1() and bar2() (dummy) defined as:

template<typename T>
int bar1( const T& t )
{
    return 42;
}
template<typename T>
int bar2( const T& t )
{
    return 43;
}

This works fine, as demonstrated here

Now my real problem: the types A and B are actually templated by some underlying type:

template<typename T>
struct A
{
    T data;
}
template<typename T>
struct B
{
    T data;
}

And I want to be able to build this:

int main()
{
    std::vector<A<int>> a;
    std::list<B<float>> b;
    std::cout << foo(a) << '\n';   // print 42
    std::cout << foo(b) << '\n';   // print 43
}

My problem is: I don't know how to "extract" the contained type: I tried this:

template<
    typename U,
    typename F,
    typename std::enable_if<
        ( IsContainer<U>::value && std::is_same<typename U::value_type,A<F>>::value ),U
    >::type* = nullptr
>   
auto foo( const U& data )
{
    return bar1(data);
}

template<
    typename U,
    typename F,
    typename std::enable_if<
        ( IsContainer<U>::value && std::is_same<typename U::value_type,B<F>>::value ), U
    >::type* = nullptr
>   
auto foo( const U& data )
{
    return bar2(data);
}

But this fails to build:

template argument deduction/substitution failed:
main.cpp:63:21: note:  couldn't deduce template parameter 'F'

(see live here )

Q: How can I make this work?

Side note: please only C 14 (or 17) if possible, I would rather avoid C 20 at present.

CodePudding user response:

You can define traits for isA/isB:

template <typename T> struct IsA : std::false_type { };
template <typename T> struct IsA<A<T>> : std::true_type  { };

template <typename T> struct IsB : std::false_type { };
template <typename T> struct IsB<B<T>> : std::true_type  { };

and SFINAE based on those traits.

Alternative might be tag dispatching:

template<typename T> int bar1( const T& t ) { return 42; }
template<typename T> int bar2( const T& t ) { return 43; }

template <typename T> struct Tag{};

template<typename T, typename U>
int bar(const T& t, Tag<A<T>>) { return bar1(t); }

template<typename T, typename U>
int bar(const T& t, Tag<B<U>>) { return bar2(t); }

template<
    typename T,
    typename std::enable_if<IsContainer<T>::value>::type* = nullptr
    >
auto foo( const U& data ) -> decltype(bar(data, Tag<typename T::value_type>{}))
{
    // ... call some other function
    return bar(data, Tag<typename T::value_type>{});
}

CodePudding user response:

There is a simple solution: define a trait IsA in exactly the same way you defined IsContainer:

template<class> struct IsA : std::false_type {};
template<class T> struct IsA<A<T>> : std::true_type {};

and then write

IsContainer<U>::value && IsA<typename U::value_type>::value

Depending on your exact use case, you might even not need the first trait, because for U = std::map<...>, IsA<typename U::value_type>::value will be false anyway.

  • Related