-1

I have a problem with SFINAE. I'm missing something important and reading other issues do not help :(

Here is the code:

#include <string>
#include <type_traits>
#include <iostream>

// version with template class

template<typename T, typename = std::enable_if_t<
                std::is_integral<std::remove_reference_t<T>>::value
             || std::is_floating_point<std::remove_reference_t<T>>::value
             >
         >
struct to_my_string
{
    std::string operator()(T numerical)
    {
        return std::to_string(numerical);
    }
};

template<typename T, typename = std::enable_if_t< std::is_same<T, std::string>::value > >
struct to_my_string
{
    std::string operator()(T str)
    {
        return "'" + str + "'";
    }
};


// version with template method

template<typename T, typename = std::enable_if_t<
                std::is_integral<std::remove_reference_t<T>>::value
             || std::is_floating_point<std::remove_reference_t<T>>::value
             >
         >
std::string to_string_method(T numerical)
{
    return std::to_string(numerical);
}

template<typename T, typename = std::enable_if_t< std::is_same<T, std::string>::value > >
std::string to_string_method(T str)
{
    return "'" + str + "'";
}

int main()
{
    std::cout << "start" << std::endl;
    return 0;
}

I have pasted two approaches - a class and a method approach. Both yields similar errors: Here are errors for g++ (clang gives similar errors):

g++ --std=c++14 sfinae_failure.cpp

sfinae_failure.cpp:21:12: error: redefinition of default argument for ‘class<template-parameter-1-2>’
 struct to_my_string
        ^
sfinae_failure.cpp:7:26: note: original definition appeared here
 template<typename T, typename = std::enable_if_t<
                      ^
sfinae_failure.cpp:43:17: error: redefinition of ‘template<class T, class> std::__cxx11::string to_string_method(T)’
 std::string to_string_method(T str)
             ^
sfinae_failure.cpp:37:17: note: ‘template<class T, class> std::__cxx11::string to_string_method(T)’ previously declared here
 std::string to_string_method(T numerical)
             ^

My main question is why it does not work, even when I'm using T type in enable_if_t. What am I missing?

Additional side question is - I thought that template classes/methods are instantiated, when they are used. As you can see in the main method - they are not.

I would really appreciate some help here even if that topic was touched many many times...

wrealcon
  • 409
  • 3
  • 7
  • 1
    Possible duplicate: https://stackoverflow.com/q/15427667/1405560 – Maximus Feb 07 '18 at 22:40
  • 1
    A common mistake is to declare two function templates that differ only in their default template arguments. This is illegal because default template arguments are not part of function template's signature, and declaring two different function templates with the same signature is illegal http://en.cppreference.com/w/cpp/types/enable_if – Maximus Feb 07 '18 at 22:42
  • For the struct-based version, you need to have a base template (probably an empty struct in this case) which is then *specialized* for your cases. – Daniel Schepler Feb 07 '18 at 22:48
  • SFINAE aside, you are not correctly implementing **template specialization**. Learn how to do template specialization first, and then try adding SFINAE to your specializations. – Drew Dormann Feb 07 '18 at 22:55
  • @DrewDormann, I think I had some kind of blackout yesterday when it comes to class template specialization :| – wrealcon Feb 08 '18 at 08:16

2 Answers2

2

The error for your class template is because you can't redefine a class template, it has nothing to do with SFINAE.

template<class A = int> struct foo {};
template<class A = double> struct foo {};

This code can't compile even without instantiation.

The error for your function is because default template argument is not part of a function's template parameter signature.

template<class A, class B = int> void bar() {}
template<class A, class B = double> void bar() {}

This code can't compile even without instantiation, because their template parameter signature are the same:

template<class A, class B> void bar() {}

thus redefinition.

llllllllll
  • 16,169
  • 4
  • 31
  • 54
  • Thank you! I have no idea why I've started to try to override base template instead of focusing on partial specialization. The methods though are still a mystery to me - the solution below works, but I'm not sure why I have to do it like that. More in the comment below. – wrealcon Feb 08 '18 at 07:44
  • In solution below, the enable_if_t becomes a non-type template parameter, thus 2 function have essentially different template parameters. Your original solution enable_if_t is only a default a argument, the template parameter are the same (both an anonymous type parameter). – llllllllll Feb 08 '18 at 07:54
  • @wrealcon: see [default-template-argument-when-using-stdenable-if-as-templ-param](https://stackoverflow.com/questions/38305222/default-template-argument-when-using-stdenable-if-as-templ-param-why-ok-wit) – Jarod42 Feb 08 '18 at 09:11
1

Fixed versions:

template<typename T, typename Enabler = void> struct to_my_string;

template <typename T>
struct to_my_string<T, std::enable_if_t<
                std::is_integral<std::remove_reference_t<T>>::value
             || std::is_floating_point<std::remove_reference_t<T>>::value
             >>
{
    std::string operator()(T numerical) const
    {
        return std::to_string(numerical);
    }
};

template<typename T>
struct to_my_string<T, std::enable_if_t<std::is_same<T, std::string>::value>>
{
    std::string operator()(const T& str) const
    {
        return "'" + str + "'";
    }
};


// version with template method
template<typename T, std::enable_if_t<
                std::is_integral<std::remove_reference_t<T>>::value
             || std::is_floating_point<std::remove_reference_t<T>>::value
             , void*> = nullptr
         >
std::string to_string_method(T numerical)
{
    return std::to_string(numerical);
}

template<typename T, std::enable_if_t< std::is_same<T, std::string>::value, void*> = nullptr>
std::string to_string_method(T str)
{
    return "'" + str + "'";
}
Jarod42
  • 203,559
  • 14
  • 181
  • 302