Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Selecting traits with enable_if - works with clang, but not with gcc

I am working on a generic piece of (C++11) code that is supposed to work with boost::multi_array, Eigen::Matrix, and possibly other types of n-dimensional arrays. At several points I need access to the element-type of a given array-type. The boost arrays contain a typedef called Element, while the Eigen arrays contain a typedef called Scalar.

What I want is a type-trait that returns the element type of a given array type. Unfortunately, I cannot just template specialize the trait class for all possible array types, because Eigen uses expression-templates, and hence, there are infinitely many types of Eigen matrices. Therefore, I am using SFINAE with enable_if to implement my traits. The criterion which enable_if should select on is, whether a type has a typedef called Element, or one called Scalar.

For this to work I implemented type-traits has_element, and has_scalar, which determine whether the corresponding typedef exists. My implementation is inspired by this blog post about type-requirements.

template <class T>
using ToVoid = void;

template <class T, class Enable = void>
struct has_scalar : public std::false_type {};

template <class T>
struct has_scalar<T, ToVoid<typename T::Scalar>> : public std::true_type {};

template <class T, class Enable = void>
struct has_element : public std::false_type {};

template <class T>
struct has_element<T, ToVoid<typename T::Element>> : public std::true_type {};

The idea is, that the compiler will pick the false_type version if the typedef is not presented, and will pick the more specialized true_type version if the typedef is present.

The actual trait to obtain the scalar type is implemented like this:

template <class Condition, class T = void>
using EnableIf = typename std::enable_if<Condition::value, T>::type;

template <class T, class Enable = void>
struct scalar_of;

template <class T>
struct scalar_of<T, EnableIf<has_element<T>>> {
    using type = typename T::Element;
};

template <class T>
struct scalar_of<T, EnableIf<has_scalar<T>>> {
    using type = typename T::Scalar;
};

template <class T>
using ScalarOf = typename scalar_of<T>::type;

A simple example use case is this:

struct BoostArray {
    using Element = double;
};

struct EigenArray {
    using Scalar = float;
};


int main() {
    using std::is_same;
    assert((is_same<double, ScalarOf<BoostArray>>::value));
    assert((is_same<float, ScalarOf<EigenArray>>::value));
}

Now, the odd thing is that this works just fine with Clang 3.4. However, GCC 4.8.1 fails to compile this and gives the following error message:

test.cpp: In substitution of ‘template<class T> using ScalarOf = typename scalar_of<T>::type [with T = BoostArray]’:
test.cpp:51:5:   required from here
test.cpp:37:45: error: ambiguous class template instantiation for ‘struct scalar_of<BoostArray, void>’
 using ScalarOf = typename scalar_of<T>::type;
                                             ^
test.cpp:27:8: error: candidates are: struct scalar_of<T, typename std::enable_if<has_element<T, void>::value, void>::type>
 struct scalar_of<T, EnableIf<has_element<T>>> {
        ^
test.cpp:32:8: error:                 struct scalar_of<T, typename std::enable_if<has_scalar<T, void>::value, void>::type>
 struct scalar_of<T, EnableIf<has_scalar<T>>> {

The clang version is here and works fine. The gcc version is here and fails to compile.


My Question: Is my code correct, and this an issue with GCC 4.8.1; or am I doing something wrong and Clang is being too generous when compiling? In any case, how can I change my code such that GCC 4.8.1 would compile it?

like image 786
Lemming Avatar asked Dec 26 '22 03:12

Lemming


1 Answers

Looks like it's yet another manifestation of CWG issue 1558. The standard was unclear whether unused template arguments in alias templates can result in substitution failure. Clang treats it as substitution failure; GCC simply ignores the unused arguments.

Use

template<typename... Ts>
struct make_void { typedef void type;};

template<typename... Ts>
using ToVoid = typename make_void<Ts...>::type;

instead and you should see it compile in both compilers.

like image 115
T.C. Avatar answered Jan 17 '23 13:01

T.C.