select method code depending on template value

193 views Asked by At

I've got a template c++ object as follows

template <typename T, Dimension D>
class Field : public std::vector<T>
{
    // ... lot of stuff ...
    T differentiate(const gridPoint<D>&, int) const;
};

This differentiate methode is computed differently depending of the Dimension D

enum Dimension : std::size_t { _2D = 2, _3D = 3 };

I could just put a switch inside the method's body bt I'd like to use the templates in order to help with clarity

I tried using std::enable_if like this:

template <typename T, Dimension D>
typename std::enable_if<D==_2D, T>::type
Field<T,D>::differentiate(const gridPoint<D>& pt, int extent) const
{
    // ... lot of computation
}

template <typename T, Dimension D>
typename std::enable_if<D==_3D, T>::type
Field<T,D>::differentiate(const gridPoint<D>& pt, int extent) const
{
    // ... even more computation
}

but the compiler tels me that my implementation doesn't match any prototypes

What did I do wrong ? I just can't figure out how i'm suppose to declare the method's code

3

There are 3 answers

1
sfjac On

For SFINAE to work, I believe the function needs to be templated so that this becomes a choice of which function compiles during overload resolution, not which function compiles during class instantiation.

I modified this as follows and it "works" on this end:

#include <iostream>
#include <vector>

enum Dimension : std::size_t { _2D = 2, _3D = 3 };

template <Dimension D>
struct gridPoint
{
    int d[D];
};

template <typename T, Dimension D>
struct Field : public std::vector<T>
{
    template <Dimension D2>
    typename std::enable_if<D== D2 && D==_2D, T>::type
    differentiate(const gridPoint<D2>& pt, int extent) const
    {
        std::cout << "2D differentiate called" << std::endl;
        return T(0.0);
    }

    template <Dimension D2>
    typename std::enable_if<D==D2 && D==_3D, T>::type
    differentiate(const gridPoint<D2>& pt, int extent) const
    {
        std::cout << "3D differentiate called" << std::endl;
        return T(0.0);
    }
};

int main() {
    Field<double, _2D> foo;
    gridPoint<_2D> point { 3, 4 };
    foo.differentiate(point, 3);

    gridPoint<_3D> p3 { 3, 4, 5 };
    Field<double, _3D> bar;
    bar.differentiate(p3, 8);

    return 0;
}

I didn't sort out the template foo to get this to compile with the definition out-of-line.

2
Casey On

You can probably save yourself a lot of hassle and unreadable code in the long run by writing distinct partial specializations of Field for the 2D and 3D case:

enum Dimension : std::size_t { _2D = 2, _3D = 3 };

template <Dimension D>
using gridPoint = std::array<int, D>;

template <typename T>
struct Field_base : std::vector<T> {
    // Stuff common to both specializations goes here.
    using std::vector<T>::vector;
};

template <typename, Dimension>
struct Field;

template <typename T>
struct Field<T, _2D> : Field_base<T>
{
    using grid_point = gridPoint<_2D>;
    using Field_base<T>::Field_base;

    T differentiate(const grid_point&, int) const
    {
        std::cout << "2D differentiate called\n";
        return {};
    }
};

template <typename T>
struct Field<T, _3D> : Field_base<T>
{
    using grid_point = gridPoint<_3D>;
    using Field_base<T>::Field_base;

    T differentiate(const grid_point&, int) const
    {
        std::cout << "3D differentiate called\n";
        return {};
    }
};
0
Mike Kinghan On

The compiler error provoked by your attempted out-of-line SFINAE definitions for T Field<T,Dimension>::differentiate(const gridPoint<D>&, int) const would be:

error: prototype for ‘typename std::enable_if<(D == _2D), T>::type Field<T, D>::differentiate(const gridPoint<D>&, int) const’ does not match any in class ‘Field<T, D>’
error: candidate is: T Field<T, D>::differentiate(const gridPoint<D>&, int) const
error: prototype for ‘typename std::enable_if<(D == _3D), T>::type Field<T, D>::differentiate(const gridPoint<D>&, int) const’ does not match any in class ‘Field<T, D>’
error: candidate is: T Field<T, D>::differentiate(const gridPoint<D>&, int) const

or words to that effect.

The compiler insists that any purported out-of-line definition of a member function of Field<T,Dimension> has the same prototype as the declaration of of some member function, and its diagnostics spell out that this requirement is not satisfied for either of the purported out-of-line definitions.

It is no good protesting that if the compiler would just carry on and do the SFINAE, it then would discover that the one surviving out-of-line definition matches a member function declaration. It can't do the SFINAE until it attempts some instantiation of Field<D,Dimension>, and making sure that any out-of-line template/class member definitions pair off with template/class member declarations comes earlier on its to-do list than instantiating templates. Instantiation might never happen, but orphan member definitions are always wrong.

So, both of those SFINAE-embellished prototypes would have to appear as member function declarations.

But then, if the compiler is to tolerate both of these SFINAE-embellished member function declarations, they must be template member functions (not merely member functions of a class template) and their respective std::enable_if conditions must depend upon a template parameter of the member function. Such are the SFINAE rules.

Summing up, what you need to write to accomplish your out-of-line SFINAE definitions is illustrated by the following program:

#include <iostream>
#include <vector>

enum Dimension : std::size_t { _2D = 2, _3D = 3 };

template<Dimension D>
struct gridPoint {}; // ...whatever

template <typename T, Dimension D>
class Field : public std::vector<T>
{
public:
    template <Dimension Dim = D>
    typename std::enable_if<Dim ==_2D, T>::type
    differentiate(const gridPoint<Dim>&, int) const;

    template <Dimension Dim = D>
    typename std::enable_if<Dim ==_3D, T>::type
    differentiate(const gridPoint<Dim>&, int) const;
};

template <typename T, Dimension D> template<Dimension Dim>
typename std::enable_if<Dim ==_2D, T>::type
Field<T,D>::differentiate(const gridPoint<Dim>& pt, int extent) const
{
    std::cout << "_2D differentiate" << std::endl;
    return T(); // ...whatever
}

template <typename T, Dimension D> template<Dimension Dim>
typename std::enable_if<Dim ==_3D, T>::type
Field<T,D>::differentiate(const gridPoint<Dim>& pt, int extent) const
{
    std::cout << "_3D differentiate" << std::endl;
    return T(); // ...whatever
}


int main()
{
    Field<int,_2D> f_2d;
    gridPoint<_2D> gp_2d;
    f_2d.differentiate(gp_2d,2);
    Field<float,_3D> f_3d;
    gridPoint<_3D> gp_3d;
    f_3d.differentiate(gp_3d,3);
    f_3d.differentiate(gp_2d,2);
    return 0;
}

In this not very pleasant light, you might possibly want to review the question of whether Dimension needs to be a template parameter of Field, or whether it might just be a template parameter of member functions of Field. As I don't know the complete implementation of the template, I can't say. Alternatively, you might reconsider your dislike of the template base class + partial specialization approach suggested by @casey.

Presumably you would like the alternative definitions of differentiate out-of-line because they are big and you don't want them sprawling in the body of the class template. In thorny cases like this a plodding but fairly failsafe way of extruding template/class member definitions to be out-of-line is first to code the template/class with inline definitions and get a successful build; then copy-paste the inline definitions to their out-of-line places, add the required template/class qualifications and delete default specifiers; then truncate the original in-line definitions to declarations; then get a successful build again.

The output of that example program is:

_2D differentiate
_3D differentiate
_2D differentiate

The last line is emitted by the execution f_3d.differentiate(gp_2d,2), which draws attention to the fact that the selected implementation of differentiate is determined by the Dimension of the gridPoint<Dimension>& argument that is passed to it and not by the Dimension of the Field<T,Dimension> on which it is invoked. Thus we can call Field<T,_3D>::differentiate<_2D>. Since you said:

This differentiate methode is computed differently depending of the Dimension D

this on the face of it seems to be the behaviour you want, as the gridPoint<Dimension> argument differentiates the implementations of differentiate according to the value of Dimension. But this observation revives the question: Is there really a good reason for Dimension to be a template parameter of Field, and not just a template parameter of differentiate?

If there is really a good reason, then you want it to be impossible to call Field<T,_3D>::differentiate<_2D> or Field<T,_2D>::differentiate<_3D>. You can achieve that by replacing all occurrences of <Dim> with <D> in the program, though the SFINAE implementation then looks even more laboured.