Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I use SFINAE to choose the closest matching type trait?

Scenario:
I have a variety of types that can be classified as sequence containers.
All sequence containers are data structures, but not every data structure will be a sequence container.

Here is an example of that illustrated in code. The only "important type" involved in this example, is the Array_T. It falls into two categories: It's a sequence container, and since all sequence containers are data structures, it is in turn, a data structure.

//A sequence container type
class Array_T{};

//A type trait for that particular sequence container
template <typename T> struct Is_Array           { static const bool value = false; };
template <>           struct Is_Array<Array_T>  { static const bool value = true;  };

//A type trait to identify all of the sequence containers
template <typename T> struct Is_A_Sequence_Container { static const bool value = Is_Array<T>::value
/* would probably "or" together more sequence types, but we only have Array_T in this example */;};

//A type trait to identify all of the data structures
template <typename T> struct Is_A_Data_Structure { static const bool value = Is_A_Sequence_Container<T>::value
/* would probably "or" together more data structure types, but we only have sequence containers in this example */;};

Please note that no inheritance can be done on Array_T; it must remain the way it has been declared.


Problem:
I want to write two functions. One function will handle all of the sequence containers, and the other function will handle all of the data structures. I don't know whether or not the sequence container function will actually exist or not, since that portion of the code may or may not get generated.

So, how can I use meta-template programming, to choose the closest matching identification for the type? Here are two examples of the expected behaviour:

Case 1:

// ...
//Both functions exist! Call the more specific one.
// ...

function(Array_T{}); // prints "sequence container"

Case 2:

// ...
//Only the data structure one exists(not the sequence container one)
// ...

function(Array_T{}); // prints "data structure"

My attempt so far:

#include <iostream>
#include <type_traits>

//A sequence container type
class Array_T{};

//A type trait for that particular sequence container
template <typename T> struct Is_Array           { static const bool value = false; };
template <>           struct Is_Array<Array_T>  { static const bool value = true;  };

//A type trait to identify all of the sequence containers
template <typename T> struct Is_A_Sequence_Container { static const bool value = Is_Array<T>::value
/* would probably "or" together more sequence types, but we only have Array_T in this example */;};

//A type trait to identify all of the data structures
template <typename T> struct Is_A_Data_Structure { static const bool value = Is_A_Sequence_Container<T>::value
/* would probably "or" together more data structure types, but we only have sequence containers in this example */;};

// ↑ all of this code was already shown to you


//NOTE: This function MAY OR MAY NOT actually appear in the source code
//This function handles all sequence types
template<class T, typename std::enable_if<Is_A_Sequence_Container<T>::value,int>::type=0>
void function(T t) {
    std::cout << "sequence container" << std::endl;
    return;
}

//This function handles all data structures; assuming a more specific function does not exist(*cough* the one above it)
template<class T, typename std::enable_if<Is_A_Data_Structure<T>::value,int>::type=0>
void function(T t) {
    std::cout << "data structure" << std::endl;
    return;
}

int main(){

    function(Array_T{});
}

Now I realize this doesn't work, because the value will be true for BOTH of the enable_ifs.
So I want to add a second enable_if onto the data structure function to check to see if the sequence container function exists. Something like this:

//...
//This function handles all data structures; assuming a more specific function does not exist(*cough* the one above it)
template<class T, typename std::enable_if<Is_A_Data_Structure<T>::value,int>::type=0,
                  typename std::enable_if</*if the more specific function does not exist*/,int>::type=0>>
void function(T t) {
    std::cout << "data structure" << std::endl;
    return;
}

int main(){

    function(Array_T{});
}

And that's where I'm stuck. Is there a way to do this without touching the Array_T deceleration, and without involving a third function for dispatching?

like image 820
Trevor Hickey Avatar asked Dec 21 '13 10:12

Trevor Hickey


2 Answers

I would use Tag dispatching:

struct DataStructureTag {};
struct SequenceContainerTag : public DataStructureTag {};

template <typename T> struct DataStructureTagDispatcher
{
    typedef typename std::conditional<Is_A_Sequence_Container<T>::value,
                                      SequenceContainerTag,
                                      DataStructureTag>::type type;
};


// NOTE: This function MAY OR MAY NOT actually appear in the source code
// This function handles all sequence types
template<class T>
void function(T&& t, const SequenceContainerTag&) {
    std::cout << "sequence container" << std::endl;
    return;
}

// This function handles all data structures (not handled my a more specific function)
template<class T>
void function(T&& t, const DataStructureTag&) {
    std::cout << "data structure" << std::endl;
    return;
}

template <class T>
typename std::enable_if<Is_A_Data_Structure<T>::value, void>::type
function(T&& t)
{
    typedef typename DataStructureTagDispatcher<T>::type tag;
    function(t, tag());
}
like image 140
Jarod42 Avatar answered Oct 06 '22 01:10

Jarod42


You can also use a class hierarchy to disambiguate the overloads

struct R2 {};
struct R1 : R2 {};

//NOTE: This function MAY OR MAY NOT actually appear in the source code
//This function handles all sequence types
template<class T, typename std::enable_if<Is_A_Sequence_Container<T>::value,int>::type=0>
void function(R1, T t) {
    std::cout << "sequence container" << std::endl;
    return;
}

//This function handles all data structures; assuming a more specific function does not exist(*cough* the one above it)
template<class T, typename std::enable_if<Is_A_Data_Structure<T>::value,int>::type=0>
void function(R2, T t) {
    std::cout << "data structure" << std::endl;
    return;
}

int main(){
    function(R1{}, Array_T{});
}
like image 20
Alex Avatar answered Oct 06 '22 00:10

Alex