c++ enable_if for non-type template parameters
Asked Answered
C

3

9

I'm a bit confused about partial template specialization... I have some code that depends on an arithmetic data type T, and on a small integer DIM. I want to be able to specify different class methods for different values of DIM. The impossibility of using partial template specialization led me to explore enable_if. It's exactly what I needed... except I want it to return a number and not a type. How can I do that? The following code should illustrate what I want.

#include <stdio.h>
#include <iostream>
#include <type_traits>

template <typename T, int DIM>
class foo{
    public:
        T function();

};


template <typename T, int DIM>
T foo<T, std::enable_if<DIM == 1>::value>::function(){
    // do something
    return 1.0;
}

template <typename T, int DIM>
T foo<T, std::enable_if<DIM == 2>::value>::function(){  
    // do something else
    return 2342.0;
}

int main(){
    foo<int, 1> object;
    int ak = object.function();
    std::cout << ak << "\n";

    return 0;   
}
Crayton answered 19/12, 2017 at 2:28 Comment(0)
C
11

You can totally do what you want with enable_if, just remember, the substitution has to fail when the condition is false, so you must call type to ensure the substitution fails when specializing for various conditions.

#include <stdio.h>
#include <iostream>
#include <type_traits>

template <typename T, int DIM>
class foo
{
public:
    template <int D = DIM>
    typename std::enable_if<D == 1, T>::type
    function()
    {
        // do something
        return 1.0;
    }

    template <int D = DIM>
    typename std::enable_if<D == 2, T>::type
    function()
    {
        // do something else
        return 2342.0;
    }

};

int main(){
    foo<int, 1> object;
    int ak = object.function();
    std::cout << ak << "\n";

    return 0;
}

For simple scenarios, like the one above (where you check a specific value, rather than a range of values), you can also use partial specialization. But if you would like to specialize, say, for all values from 1-50, another for 51-200, and then a generic fallthrough, enable_if works great.

You can also use enable_if in the template signature. Just a quick example.

#include <stdio.h>
#include <iostream>
#include <type_traits>

template <typename T, int DIM>
class foo
{
public:
    template <int D = DIM, typename std::enable_if<D == 1, void>::type* = nullptr>
    T function()
    {
        // do something
        return 1.0;
    }

    template <int D = DIM, typename std::enable_if<D == 2, void>::type* = nullptr>
    T function()
    {
        // do something else
        return 2342.0;
    }

};

int main(){
    foo<int, 1> object;
    int ak = object.function();
    std::cout << ak << "\n";

    return 0;
}
Cool answered 19/12, 2017 at 2:52 Comment(3)
Nice but i think it's possible to make it a little simpler: isn't necessary mirror every template parameter (U and D) to activate SFINAE: it's enough a template parameter involved in the std::enable_if test. So should work simply as template <int D = DIM, typename std::enable_if<D == 1>::type* = nullptr> T function() (and void is the default returned type for std::enable_ifYellowish
Or also template <int D = DIM> typename std::enable_if<D == 1, T>::type function()Yellowish
@Yellowish I just made it explicit (I'm originally a Python programmer, so especially when explaining concepts, I like being explicit over implicit), but yes, both of those are completely valid suggestions and I would use your style for production code. Although removing U = T is actually a great overall suggestion. Edited.Cool
G
5

You can partial specialize the whole class:

template <typename T, int DIM>
class foo;

template <typename T>
class foo<T, 1>
{
public:
    T function() {
         // do something
         return 1.0;
     }
};

template <typename T>
class foo<T, 2>
{
public:
    T function() {
         // do something
         return 2342.0;
     }
};

If you have lot of common code between both specialization, you may still use inheritance (inherit from common part or just the specialized part).

An easy alternative way is to use Tag dispatching:

template <typename T, int dim>
class foo
{
public:
    T function();
};

 template <typename T>
 T function_helper(foo<T, 1>&) {
     // do something
     return 1.0;
 }

 template <typename T>
 T function_helper(foo<T, 2>&) {
     // do something
     return 2342.0;
 }

template <typename T, int dim>
T foo::function() {
    return function_helper(*this);
}

But in C++17, if constexpr allows simpler syntax:

template <typename T, int DIM>
class foo
{
public:
    T function() {
        if constexpr (DIM == 1) {
            // do something
            return 1.0;
        } else if constexpr (DIM == 2) {
            // do something
            return 2342.0;
        }
    } 
};

C++20 with contraints allows

template <typename T, int DIM>
class foo
{
public:
    T function() requires (DIM == 1) {
        // do something
        return 1.0;
    }
    T function() requires (DIM == 2) {
        // do something
        return 2342.0;
    } 
};
Goldston answered 19/12, 2017 at 9:41 Comment(1)
I chose the other answer because it solved my problem and yielded more explicit code, but this is very interesting! Thank you for the suggestion!Crayton
B
0

Use the std::integral_constant type to wrap your values, as in:

std::integral_constant<int, 2>
Blocking answered 19/12, 2017 at 2:41 Comment(1)
Thanks for the suggestion, but may I ask why?Crayton

© 2022 - 2024 — McMap. All rights reserved.