Attempting to define 2 different default constructors depending on template type

Hi,

I want to define 2 default constructors one for each type of the template parameter CharT with the following code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
template<typename CharT>
class MyString : public ObjectCounter<MyString<CharT>> {
	CharT* pT;
public:

	template<typename U = std::enable_if_t<std::is_same<CharT, char>::value>>
	MyString()
	{
		pT = "hello";
	}

	template<typename U = std::enable_if_t<std::is_same<CharT, wchar_t>::value>>
	MyString()
	{
		pT = L"hello";
	}
};


but it does not compile. I get these errors in VStudio 2017:


[comment]
error C2535: 'MyString<CharT>::MyString(void)': member function already defined or declared
error C2535: 'MyString<char>::MyString(void)': member function already defined or declared
// and others similar
[/comment]

I must be misusing enable_if.

Any light on this?

Regards,
Juan
enable_if affects overload resolution - it doesn't let you ignore the one-definition rule. Note that both constructors have the same signature: default template arguments don't affect that.

The usual technique is to do something like this:

1
2
3
4
5
6
7
8
template <typename T, std::enable_if_t<std::is_same<wchar_t, T>::value>>
int f_impl(int) { return 42; }

template <typename T, typename = std::enable_if_t<std::is_same<char, T>::value>>
int f_impl(long) { return 24; }

template <typename T>
int f() { return f_impl<T>(0); }


The dummy parameters to f_impl() are used to disambiguate the two overloads.

Since it's the constructor's body which has to change, I think it's fine to use the same technique, although I would be wary of overloading constructors with dummy arguments directly. I would tend towards CRTP or even a class specialization if that was necessary.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <type_traits>
template <typename CharT> class MyString {
  CharT const *pT;

  template <typename T,
            typename = std::enable_if_t<std::is_same<char, T>::value>>
  void assign_impl(int) {
    pT = "hello";
  }
  template <typename T,
            typename = std::enable_if_t<std::is_same<wchar_t, T>::value>>
  void assign_impl(long) {
    pT = L"hello";
  }
  void assign() { return assign_impl<CharT>(0); }

public:
  MyString() { assign(); }
};

int main() {
  MyString<char> x;
  MyString<wchar_t> y;
}

Last edited on
1. For the templated constructors, the type U can't be deduced; so fixing the enable_if won't solve the problem.

2. In any case, these would (should) be errors: pT = "hello"; or pT = L"hello";
(no conversion from string literal to char/wchar_t* - the const qualifier can't be dropped)

Something like this, perhaps:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <iostream>

template < typename CHAR_TYPE > struct my_literal_str {

    constexpr my_literal_str() : ptr( def_val( static_cast< const CHAR_TYPE* >(nullptr) ) ) {}

    constexpr auto c_str() const { return ptr ; }

    private:

        const CHAR_TYPE* ptr ;

        static constexpr const char* def_val( const char* ) { return "Hello (char)" ; }
        static constexpr const wchar_t* def_val( const wchar_t* ) { return L"Hello (wchar_t)" ; }

        // for types other than char/wchar_t, the default value is nullptr
        static constexpr std::nullptr_t def_val( const void* ) { return nullptr ; }
};

int main() {

    constexpr my_literal_str<char> a ;
    std::cout << a.c_str() << '\n' ; // Hello (char)

    constexpr my_literal_str<wchar_t> b ;
    std::wcout << b.c_str() << L'\n' ; // Hello (wchar_t)

    constexpr my_literal_str<int> c ;
    std::cout << c.c_str() << '\n' ; // nullptr
}

http://coliru.stacked-crooked.com/a/882a3546b5d6f44d
http://rextester.com/RJLE72277
Thanks to both of you. So, there is no way to have 2 functions with same signature that are implemented differently based on the actual template argument? Why is this? I know that the one definition rule is there, but if in each instantiation only one of the 2 functions would be instantiated there would not be a violation of the ODR.

??

Thanks again,
Juan
The std::enable_if is only evaluated when the constructor call is compiled, since it involves a template that's only available at that point. At class definition you still have two functions with the same signature.

If you just want to do different things based on T you could use an if inside the constructor. Since the if predicate can be computed at compile time, the if could get simplified into unconditional code.
> if in each instantiation only one of the 2 functions would be instantiated
> there would not be a violation of the ODR.

True.

But,
Notes
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



This (delightfully convoluted) version is fine.
The function templates have different signatures; and SFINAE will eliminate all but one of them:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <iostream>
#include <type_traits>

template < typename T > struct my_literal_str {

     // default constructor when T == char (with two template parameters)
     template< typename U = T, // 1
               typename = typename std::enable_if< std::is_same<U,char>::value >::type > // 2
     constexpr my_literal_str() : ptr( "Hello (char)" ) {}

     // default constructor when T == wchar_t (with three template parameters)
     template< typename U = T, // 1
               typename = typename std::enable_if< std::is_same<U,wchar_t>::value >::type, // 2
               typename = U > // 3
     constexpr my_literal_str() : ptr( L"Hello (wchar_t)" ) {}

     // default constructor when T != char && T != wchar_t (with four template parameters)
     template< typename U = T, // 1
               typename = typename std::enable_if< !std::is_same<U,char>::value >::type, // 2
               typename = typename std::enable_if< !std::is_same<U,wchar_t>::value >::type, // 3
               typename = U > // 4
     constexpr my_literal_str() : ptr(nullptr) {}

     constexpr auto c_str() const { return ptr ; }

    private: const T* ptr ;
};

int main() {

    constexpr my_literal_str<char> a ;
    std::cout << a.c_str() << '\n' ; // Hello (char)

    constexpr my_literal_str<wchar_t> b ;
    std::wcout << b.c_str() << L'\n' ; // Hello (wchar_t)

    constexpr my_literal_str<int> c ;
    std::cout << c.c_str() << '\n' ; // nullptr
}

http://coliru.stacked-crooked.com/a/b02061f4e2ff911e
http://rextester.com/VGQQU67472
Topic archived. No new replies allowed.