7

I want to make a constructor for a class, using any integral type, but differentiate between signed and unsigned. I don't want this to be a template on the class itself. The following is not working. Visual Studio is just saying no arguments will match.

class Thing{
public:
    template<typename Integral>
    Thing(
        typename std::enable_if<
            std::is_integral<Integral>::value &&
            !std::is_same<Integral,bool>::value &&
            std::is_signed<Integral>::value
            ,Integral
        >::type num
    ){
        //constructor using signed variable as input
    }
    template<typename Integral>
    Thing(
        typename std::enable_if<
            std::is_integral<Integral>::value &&
            !std::is_same<Integral,bool>::value &&
            !std::is_signed<Integral>::value//notice this is different
            ,Integral
        >::type num
    ){
        //constructor using unsigned variable as input
    }
};
tomatopipps
  • 299
  • 2
  • 13

2 Answers2

7

We need to move the SFINAE into the template. If we use

class Thing{
public:
    template<typename Integral, typename std::enable_if<
            std::is_integral<Integral>::value &&
            !std::is_same<Integral,bool>::value &&
            std::is_signed<Integral>::value
            ,Integral>::type* = nullptr> // will fail if type does not exist
    Thing(Integral i)
//        ^ use Integral type here
    {
        std::cout << "signed\n";
    }
    template<typename Integral, typename std::enable_if<
            std::is_integral<Integral>::value &&
            !std::is_same<Integral,bool>::value &&
            !std::is_signed<Integral>::value//notice this is different
            ,Integral>::type* = nullptr>
    Thing(Integral i)
    {
        std::cout << "unsigned\n";
    }
};

int main()
{
    int a = 10;
    Thing b(a);
    unsigned int c = 10;
    Thing d(c);
}

We get

signed
unsigned

Live Example

I also had to make the constructors public as they were private by default.

NathanOliver
  • 171,901
  • 28
  • 288
  • 402
3

The problem is that the type appears in a non-deduced context, so the compiler cannot deduce it from something like std::is_integral<Integral>::value. Try this instead:

#include <iostream>
#include <type_traits>

class Thing{
public:
    template<typename Integral>
    Thing(Integral num,
        typename std::enable_if<
            std::is_integral<Integral>::value &&
            !std::is_same<Integral,bool>::value &&
            std::is_signed<Integral>::value
            ,Integral
        >::type* = nullptr
    ){
        std::cout << "signed\n";
        //constructor using signed variable as input
    }

    template<typename Integral>
    Thing(Integral num,
        typename std::enable_if<
            std::is_integral<Integral>::value &&
            !std::is_same<Integral,bool>::value &&
            !std::is_signed<Integral>::value//notice this is different
            ,Integral
        >::type* = nullptr
    ){
        std::cout << "unsigned\n";
        //constructor using unsigned variable as input
    }
};

int main()
{
    int x{};
    unsigned int y{};
    Thing thing1(x);
    Thing thing2(y);
}

Live on Coliru

Side note: make your constructors public as otherwise you cannot instantiate your objects.

Community
  • 1
  • 1
vsoftco
  • 55,410
  • 12
  • 139
  • 252
  • You sneakily avoided pointing out that OP also needed to make the ctors `public` :-) – AndyG Feb 01 '16 at 18:29
  • @AndyG I forgot to mention this indeed, but made them `public` in my code ;) – vsoftco Feb 01 '16 at 18:29
  • 1
    Personally I'd prefer to put the `enable_if` in the template args: `template::value) && (!std::is_same::value) && (std::is_signed::value), int>::type = 0> Thing(T _in)` but it's all the same – AndyG Feb 01 '16 at 18:34
  • @AndyG I personally prefer that too, however when I wrote the answer the solution above required the least amount of editing, and Nathan already posted an answer with an alternative approach, so I'll leave mine as is. – vsoftco Feb 01 '16 at 18:37