Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ Template Branching

I have an interesting case of trying to have branch within a template function where the path is dependent on the interface implemented by the template type. This branch then determines the constructor of the return value. I am unsure if this type of branching is possible. The alternative it to split the function into two different function and have the user call the one that corresponds to the desired branch.

My question is twofold:

How can I perform the if statement based on the interface implementation?

How can I get the function to compile when the interface is not implemented? For example int does not have a constructor with two parameters.

template<typename T>
T GetObject()
{
    // If T implements interface, call interface constructor
    if(typeid(T) implements inteface IInterface) // How can this be done?
        return T(1, 2);

    // If T does not implement the interface, call parameterless constructor
    else
        return T();
}
like image 764
Russell Trahan Avatar asked Nov 18 '14 22:11

Russell Trahan


1 Answers

As you can easily deduce, your code as is has no way of working (even though I wish it did) because regardless of what you put in your if statements, every line has to be compiled - even if some will later be removed during optimization. Even if you had the correct line for:

if(typeid(T) implements inteface IInterface)
    return T(1,2); // <---

The second line would still be compiled for int, which is no good.

What you want is SFINAE: Substitution Failure Is Not An Error. Basically, you intentionally write a declaration that cannot compile but as long as there's an available overload that will compile, that's good enough! So to that end, we just take advantage of some type traits and the magic, yet incredibly simple, enable_if:

// this overload will only exist if T is-a IInterface
template<typename T>
typename std::enable_if<
    std::is_base_of<IInterface, T>::value,
    T
>::type
GetObject() {
    return T(1,2);
}

// this overload will only exist if T is NOT an IInterface
template<typename T>
typename std::enable_if<
    !std::is_base_of<IInterface, T>::value,
    T
>::type
GetObject() {
    return -1;
}

// explicit for int - put this *last*
// due to complications with specialization
template <>
int GetObject() {
    return 0;
}

Although given Herb Sutter's article on function specialization, probably better to use overloading:

template <typename T> struct empty_ { };

template <typename T>
T GetObject() { return GetObjectImpl(empty_<T>{} ); }

// the int version: overload, not specialization
int GetObjectImpl( empty_<int> ) { 
    return 0;
}

// the other two versions are the same as before
template <typename T>
typename std::enable_if<... same as before ...>::type
GetObjectImpl(empty_<T> ) {
    return T(1,2);
}
like image 71
Barry Avatar answered Sep 25 '22 08:09

Barry