Search code examples
c++templatesenable-iftypename

what is difference of two kinds of template examples?


I intended to make a class 'MyVector' has 3D coordinates(X, Y, Z).

I tried making a constructor has three types of function parameter which each parameter type satisfies std::is_arithmetic.

I made two different codes. one is working well, but the other was not working.

Here's my code.

main.cpp

#include <iostream>
#include "MyVector.h"

using namespace std;

int main()
{
    MyVector mv1 = MyVector();
    int x = 5;
    double y = 2.0;
    float z = 5.0;
    MyVector mv2 = MyVector(z, y, x);
    //MyVector mv3 = MyVector(&x);


    cout << boolalpha << is_arithmetic<int>::value << endl;
    cout << mv2;

}

MyVector.h

#pragma once

#include <type_traits>
#include <iostream>

class MyVector
{

public:
    MyVector();
    MyVector(const MyVector&);


    //This is What I wanted
    template <typename N1, typename = std::enable_if_t<std::is_arithmetic<N1>::value >
            , typename N2, typename = std::enable_if_t<std::is_arithmetic<N2>::value >
            , typename N3, typename = std::enable_if_t<std::is_arithmetic<N3>::value > >
    MyVector(const N1& x, const N2& y, const N3& z)
    {
        X = x;
        Y = y;
        Z = z;
    }

    //Working
    template <typename N, typename = std::enable_if_t<std::is_arithmetic<N>::value >>
    MyVector(const N& x)
    {
        X = 0;
        Y = 0;
        Z = 0;
    }

    //Not Working
    template <typename N, std::enable_if_t<std::is_arithmetic<N>::value >>
    MyVector(const N& x)
    {
        X = 0;
        Y = 0;
        Z = 0;
    }


private:
    float X;
    float Y;
    float Z;

public:
    friend std::ostream& operator<<(std::ostream&, const MyVector&);

};

I don't know what is difference of two below codes

1. template <typename N, typename = std::enable_if_t<std::is_arithmetic<N>::value >>
2. template <typename N, std::enable_if_t<std::is_arithmetic<N>::value >>

Solution

  • These two lines of code operate in a slightly different way:

    template <typename N, typename = std::enable_if_t<std::is_arithmetic<N>::value
    // or with name
    template <typename N, typename second = std::enable_if_t<std::is_arithmetic<N>::value
    

    define a type as template (which is unnamed in the 1st case) and provides a default value (std::enable_if...). This boils down to <N=int, second=int> in your case. This post is helpful for understanding where to use template / typename. Whereas

    2. template <typename N, std::enable_if_t<std::is_arithmetic<N>::value >>
    

    has a non-type template argument. This boils down to <N=int, enable_if<...>::type second=?> The major difference between the two versions and the reason for one version 'working' out of the box is, that this time, the value that this non-type template assumes, is not specified by default. You would need to specify it or write sth like

    3. template <typename N, std::enable_if_t<std::is_arithmetic<N>::value >* = nullptr>
    

    The following version is equivalent, but provides a name:

    4. template <typename N, std::enable_if_t<std::is_arithmetic<N>::value >* second = nullptr>
    

    TLDR

    You have to also specify the default value for the enable-if guard in the second case, which the compiler cannot deduce (or specify both template arguments when calling, which is not desired). The compiler error hints at that if the line (not working) is not exchanged with line (3) above:

    /home/juli/te.cc:37:5: note: candidate: ‘template<class N, typename std::enable_if<std::is_arithmetic<_Tp>::value, void>::type <anonymous> > MyVector::MyVector(const N&)’
       37 |     MyVector(const N& x)
          |     ^~~~~~~~
    /home/juli/te.cc:37:5: note:   template argument deduction/substitution failed:
    /home/juli/te.cc:63:30: note:   couldn’t deduce template parameter ‘<anonymous>’
       63 |     MyVector mv3 = MyVector(x);
    
    

    Depending on what standard you are using, looking into the newly introduced concepts feature of c++2a might be interesting.