1

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 >>
ssh
  • 49
  • 6
  • I tried second version first as refered [link](https://en.cppreference.com/w/cpp/types/enable_if). second version gives an error like this 'No instance of constructor "MyVector::MyVector" matches the argument list. – ssh Jan 27 '20 at 14:47
  • This [question](https://stackoverflow.com/questions/610245/where-and-why-do-i-have-to-put-the-template-and-typename-keywords) may be helpful to you. – 1201ProgramAlarm Jan 27 '20 at 14:49
  • Thank you guys! I read what you linked, It's helpful to me. – ssh Jan 28 '20 at 02:46

1 Answers1

1

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.

mutableVoid
  • 1,284
  • 2
  • 11
  • 29
  • [Running example](https://wandbox.org/permlink/DvA4i20A6BFFHSLs) – mutableVoid Jan 27 '20 at 17:02
  • This is what I exactly wanted! Thank you so much, I read a thing you linked, it also helps me a lot.

    So I understood that
    first version means a declaration of two typenames, works fine because template substitution succeeded.

    Second one includes a typename, non-type template value. non-type template value has type 'enable_if', and its pointer initialized, So when I am trying to instanciate MyVector like this,
    'MyVector mv = MyVector("ss");'
    substitution failed because of type of second non-type template value has no type.
    – ssh Jan 28 '20 at 02:43
  • 1
    'template ::value >* = nullptr>' this code you told me also works well. – ssh Jan 28 '20 at 02:43
  • I am glad my answer helped! When trying to address your comment I realized the following: I am kind of using 'inconsistent' naming in my example: In the first code blob, I assign the name 'second' to my second template argument. Therefore I want to clarify, that the two versions from that blob are equivalent, the first one leaves the template unnamed (as we do not use it in our implementation, just for SFINAE), the second one in that blob applies the name 'second' to it. (I'm just writing that in order to distinct between 2nd line in the first blob or the second blob) in what I write below – mutableVoid Jan 28 '20 at 10:17
  • So in that very first code blob, the situation is exactly as you describe it, two "type template parameters" are declared. Note however, that the reason for these versions working fine is, that you declare a **default template argument** (basically everything behind the '='). This is required, because the template argument called 'second' is not used in the function signature, hence SFINA cannot help to infer the type. The type of parameter 'N' however, can be inferred because of the value you porivide as argument. – mutableVoid Jan 28 '20 at 10:17
  • As far as the code block (2.) is concerned: You are correct in that this is a non-type template parameter, which just assumes a value. The [documentation](https://en.cppreference.com/w/cpp/types/enable_if) of enable_if specifies the type as second template parameter, which is defaulted with void. So in this case, what the compiler expects as second template parameter is a 'void'. I think I kept it general above and referred to it by `std::enable_if<...>::type` which 'holds' that type. – mutableVoid Jan 28 '20 at 10:17
  • As we cannot simply specify a void value, I appended a (*), made it to void-pointer in the 'fix' (version3). Then, it is possible to provide the default initialization as nullptr. An alternative is to specify the second template parameter of enable_if, and to write something like `std::enable_if<[CONDITION], int> = 0` – mutableVoid Jan 28 '20 at 10:18
  • 1
    I really appreciate your detailed explanation. I got it. – ssh Jan 28 '20 at 13:05