Museful
Museful

Reputation: 6959

C++ partial template specialization issue

Given a matrix class

using index_t = int;
template<index_t M, index_t N, typename S>
struct mat {
    // matrix implementation
};

I would like to have a generic way of obtaining the elementCount for a given type T that will work for both matrices and scalars. For example, I imagine being able to do this:

dimensionality<mat<1,2,double>>(); // returns 2
dimensionality<mat<2,2,float>>(); // returns 4
dimensionality<double>(); // returns 1

or perhaps something like this:

attributes<mat<1,2,double>>::dimensionality; // returns 2
attributes<mat<2,2,float>>::dimensionality; // returns 4
attributes<double>::dimensionality; // returns 1

My attempt:

I tried doing the following (thinking that I am partially specializing the struct attributes):

template<typename T>
struct attributes {};

template<typename S, typename = std::enable_if_t<std::is_arithmetic<S>::value>>
struct attributes<S> {                                        // <---  compiler error on this line
    static constexpr index_t dimensionality = 1;
};

template<index_t M, index_t N, typename S>
struct attributes<mat<M, N, S>> {
    static constexpr index_t dimensionality = M * N;
};

but I get a compiler error on the indicated line. Can you help me (either by suggesting a better approach, or to understand what I'm doing wrong)?

Upvotes: 2

Views: 70

Answers (3)

dfrib
dfrib

Reputation: 73206

Wrapping a trait-based static constant: std::integral_constant

You may want to make use of std::integral_constant from <type_traits> to implement your trait,

[...] std::integral_constant wraps a static constant of specified type. It is the base class for the C++ type traits.

as well as supply a helper variable template dimensionality_v for ease of use:

#include <type_traits>

// Default dimensionality 0.
template <class T, typename = void>
struct dimensionality : std::integral_constant<index_t, 0> {};

template <typename S>
struct dimensionality<S, std::enable_if_t<std::is_arithmetic_v<S>>>
    : std::integral_constant<index_t, 1> {};

template <index_t M, index_t N, typename S>
struct dimensionality<mat<M, N, S>> : std::integral_constant<index_t, M * N> {};

template <class T>
inline constexpr index_t dimensionality_v = dimensionality<T>::value;

DEMO.

Alternatively, if you don't want to allow a default dimensionality for types that are neither fulfilling std::is_arithmetic_v nor equals mat:

template <class T, typename = void>
struct dimensionality {};

template <typename S>
struct dimensionality<S, std::enable_if_t<std::is_arithmetic_v<S>>>
    : std::integral_constant<index_t, 1> {};

template <index_t M, index_t N, typename S>
struct dimensionality<mat<M, N, S>> : std::integral_constant<index_t, M * N> {};

template <class T>
inline constexpr index_t dimensionality_v = dimensionality<T>::value;

DEMO.

Upvotes: 1

songyuanyao
songyuanyao

Reputation: 173024

You could add another template parameter with default type void, then specify the std::enable_if as the corresponding template argument in the partial specialization for arithmetic types. (And adjust the partial specialization for mat too.)

template<typename T, typename = void>
struct attributes {};

template<typename S>
struct attributes<S, std::enable_if_t<std::is_arithmetic<S>::value>> { 
    static constexpr index_t dimensionality = 1;
};

template<index_t M, index_t N, typename S>
struct attributes<mat<M, N, S>, void> {
    static constexpr index_t dimensionality = M * N;
};

LIVE

Upvotes: 1

Piotr Skotnicki
Piotr Skotnicki

Reputation: 48527

First, the specialization cannot have more template parameters than the primary template, but your code puts typename = std::enable_if_t for the arithmetic case.

Secondly, in order to make this std::enable_if_t to work, it would need to result in something that makes the specialization more specialized than the primary template, not only valid. For that, you could use the void_t trick:

template <typename T, typename = void>
struct attributes {};

template <typename S>
struct attributes<S, std::enable_if_t<std::is_arithmetic<S>::value>> {
    static constexpr index_t dimensionality = 1;
};

This also entails that the specialization for matrices should include this void parameter as well:

template <index_t M, index_t N, typename S>
struct attributes<mat<M, N, S>, void> {
    static constexpr index_t dimensionality = M * N;
};

DEMO


Your trait could, however, be shortened to:

template <typename T>
struct attributes {
    static_assert(std::is_arithmetic<T>::value, "T must be arithmetic or mat");
    static constexpr index_t dimensionality = 1;
};

template <index_t M, index_t N, typename S>
struct attributes<mat<M, N, S>> {
    static constexpr index_t dimensionality = M * N;
};

DEMO 2

That is, just consider arithmetic types in the primary template, when no specialization matches.

Upvotes: 1

Related Questions