0

I have a template where a function is overloaded so it can handle both an std::string parameter and the type of parameter that the template gets instantiated with. This works fine except when the template is being instantiated with std::string, since this results in two member functions with the same prototype. Thus, I have chosen to specialize that function for this particular case. However, it seems like the compiler (g++ 4.8.1 with flag -std=c++0x) never gets to the point where the specialization is actually overriding the primary template and it complains about the ambiguous overload the before it seems to realize that it should use the specialization. Is there a way to get around this?

#include <iostream>

template<class T>
struct A {
    std::string foo(std::string s) { return "ptemplate: foo_string"; }
    std::string foo(T e) { return "ptemplate: foo_T"; }
};

template<> //Error!
std::string A<std::string>::foo(std::string s) { return "stemplate: foo_string"; }

int main() {
    A<int> a; //Ok!
    std::cout << a.foo(10) << std::endl; 
    std::cout << a.foo("10") << std::endl;

    //A<std::string> b; //Error!
    //std::cout << a.foo("10") << std::endl;
    return 0;
}

This results in compile errors, even if I don't instantiate at all with std::string (it seems that the compiler instantiates with std::string as soon as it sees the specialization and that it, before it actually processes the specialization, complains about the ambiguous overload which the specialization, in turn, will "disambiguate").

Compiler output:

p.cpp: In instantiation of 'struct A<std::basic_string<char> >':
p.cpp:10:27:   required from here
p.cpp:6:14: error: 'std::string A<T>::foo(T) [with T = std::basic_string<char>; std::string = std::basic_string<char>]' cannot be overloaded
  std::string foo(T e) { return "ptemplate: foo_T"; }
              ^
p.cpp:5:14: error: with 'std::string A<T>::foo(std::string) [with T = std::basic_string<char>; std::string = std::basic_string<char>]'
  std::string foo(std::string s) { return "ptemplate: foo_string"; }
              ^

I would like it to just skip through the implementation of foo() in the primary template and use the specialization without considering the primary template foo(). Could it be done somehow, maybe with non-type template parameters, or do I have to make a fully specialized class template for std::string with all the code duplication it implies (I prefer not to use inheritance here)... Other suggestions?

fast-reflexes
  • 4,891
  • 4
  • 31
  • 44

4 Answers4

2

When you specilize your member function you still get the double ambiguous declaration. Waht you need is to specialize the struct template:

template<>
struct A<std::string> {
    std::string foo(std::string s) { return "ptemplate: foo_string"; }
};

If there are many members to the A struct maybe you can refactor:

template<typename T>
struct Afoo
{
    std::string foo(T s) { ... }
    std::string foo(std::string s) { ... }
};
template<>
struct Afoo<std::string>
{
    std::string foo(std::string s) { ... }
};
template<typename T>
struct A : Afoo<T>
{
    //a lot of code
};
rodrigo
  • 94,151
  • 12
  • 143
  • 190
  • Aha! But then I need to redeclare all other member functions and variables in that specialization right? Also, what does adding the specialization the way I did actually do? If it wasn't for the overload, the compiler accepts it but I can see that it doesn't result in anything though... – fast-reflexes Oct 30 '14 at 10:35
  • @fast-reflexes: Yes, you may have a lot of duplicated code unless you refactor your code (think of a `Afooer` base class?). – rodrigo Oct 30 '14 at 10:38
  • @fast-reflexes: Ok, I'm correcting myself, my comments about function template specialization are irrelevant, as your function is not a template per se, but a function member of a template struct. You _can_ specialize a template member function, but you will not avoid the double declaration unless you specialize the struct. – rodrigo Oct 30 '14 at 10:40
  • That is true, I just did some attempts and it works fine to specialize member functions that way... ok so then we're back to where we started... I just want the compiler to neglect the primary template in that case since it will cause ambiguity that will be resolved by the specialization but I guess you're right that as long as I use the same template, there will be ambiguity since the compiler first checks the primary template and THEN specializations... I think that nyarlathotep108s answer might work though... – fast-reflexes Oct 30 '14 at 10:44
  • 2
    @fast-reflexes: Not exactly, the compiler will first check the member function declaration inside `A` and fail because of the duplicate overload. The member function specialization cannot change that: it changes the _definition_ of the functions, not the _declaration_. – rodrigo Oct 30 '14 at 10:48
  • So in your example, instantiating with std::string won't ever use the AFoo template but will always use the AFoo specialization? – fast-reflexes Oct 30 '14 at 10:55
  • @fast-reflexes: Yes, precisely. Note that it can be a problem if `Afoo::foo` needs to access the other members of `A`. If that were the case, you could declare `foo` as `static std::string foo(A *that, T s)`, make it friend and so on... – rodrigo Oct 30 '14 at 11:06
2

I'm going to answer this myself since I've been diving deep into this subject today and I think these solutions are nice. All other posts up to this point have been contributive and have had attractive details with potential in other situations. However, I preferred to do it with these things in mind:

  • Avoid the use of more than one class template
  • Avoid too complicated specializations as far as possible
  • Avoid using inheritance and refactor into base and derived classes
  • Avoid the use of extra wrappers

Please feel free to comment before I accept it as my answer.

Another good and inspiring post on the subject focusing on the use of member function overloading rather than specializations can be found at explicit specialization of template class member function

Solution 1

template<class T>
struct A {
    template<class V = T> std::string foo(T) { return "foo_T"; }
    std::string foo(std::string) { return "foo_std::string"; }
    std::string foo(const char *) { return "foo_const char *"; }
};

template<> template<> 
std::string A<std::string>::foo(std::string s) { return foo(s); }

I think this is a dense and understandable solution allowing all class instantiations to use foo(std::string) and foo(const char *) (for passing a string as an rvalue). The use of a dummy template parameter effectively stops class instantiations with std::string from resulting in ambiguous overloads at the same time as the actual template argument hinders uncontrolled function instantiations with unpredictable function arguments. The only problem might come from a class instantiation with std::string that might use the template instead of the regular member function if explicitly called with foo<std::string>(std::string) in which way I would want the class to use the regular foo(std::string) instead of the function template for other instantiations. This is resolved by using a single template specialization.

Solution 2

template<class T>
struct A {
        template<class V> std::string foo(V s) { return foo_private(s); } 

    private:
        template<class V = T> std::string foo_private(T) { return "foo_T"; }
        std::string foo_private(const char *) { return "foo_const char *"; }
        std::string foo_private(std::string) { return "foo_std::string"; }
};

This version allows us to skip the specialization to the benefit of a second template in the class declaration.

Both versions used with:

int main() {
    A<int> a;
    std::cout << a.foo(10) << std::endl; 
    std::cout << a.foo("10") << std::endl;

    A<std::string> b;
    std::cout << b.foo<std::string>("10") << std::endl;
    std::cout << b.foo("10") << std::endl;

    return 0;
}

... outputted:

foo_T
foo_const char *
foo_const char *
foo_std::string
Community
  • 1
  • 1
fast-reflexes
  • 4,891
  • 4
  • 31
  • 44
1

The error is saying that you ended up creating two method with the same signature. That is because the struct has been templated with a std::string as parameter.

You should made the function as a templated function, using its own template parameters 'K' not related to the structure template parameter 'T'. Then you can achieve template specialization for the function only.

nyarlathotep108
  • 5,275
  • 2
  • 26
  • 64
  • That's unwieldy. You always want `K` to be the same as `T` so allowing them to differ is silly! – Lightness Races in Orbit Oct 30 '14 at 10:48
  • @LightnessRacesinOrbit nothing silly, it is perfectly normal having template structure with template functions. It depends on what you want to achieve. – nyarlathotep108 Oct 30 '14 at 11:05
  • It's not silly to do that incredibly broad thing, no. It's not an applicable solution here though. – Lightness Races in Orbit Oct 30 '14 at 11:47
  • I actually don't think this is silly as long as you think about the consequences and limits the use of the templated function (so it won't result in any unpredicted behaviour such as expecting only parameters of the same type as the class instantiation but allowing anything to be passed in an uncontrolled fashion) – fast-reflexes Oct 31 '14 at 00:54
1

I admit that the solution I offer below, is a hacky solution indeed, but it does accomplish what you're trying to do and it's kinda funny. Please consider it thoroughly before you use this ;-)

I work around the issue by creating a new type, called FakeType, which can be constructed from your template-type T. The second overload of foo is now for FakeType<T> instead of T, so even when T == string there will be two different overloads:

template <typename T>
struct FakeType
{
    T t;
    FakeType(T const &t_): t(t_) {}
    operator T() { return t; }
};

template <typename T>
struct A
{
    string foo(string s) { return "ptemplate: foo_string"; }
    string foo(FakeType<T> e) { return "ptemplate: foo_T"; }
};

For the case that T != string:

A<int>().foo("string"); // will call foo(string s)
A<int>().foo(1); // will call foo(FakeType<int> e)

In the latter case, the int will be promoted to a FakeType<int>, which can be used as a regular int through the conversion operator.

For the case that T == string:

A<string>().foo("string"); // will still call foo(string s)

Because the compiler will always prefer an overload for which no promotion is necessary.

PS. This approach assumes that foo is going to get its arguments either by value, or by const-reference. It will break as soon as you try to pass by reference (this can be fixed).

JorenHeit
  • 3,877
  • 2
  • 22
  • 28
  • I don't think it's that hacky apart from the type being called FakeType :) I like this solution! The operator() on FakeType is just for convenience right? I will pass my own answer to this question after som inquiries and if there's anything wrong with it, I'd be delighted to hear it. Thanks! – fast-reflexes Oct 31 '14 at 00:53
  • @fast-reflexes I'm glad you like it! Yes, the `operator T()` is for convenience. It allows you to use a `FakeType` as if it were an `int`. It's like a get-method that returns the member by reference. If you need more help (e.g. when you want the `foo` method to accept its arguments by non-const reference), let me know. Also, note that the implementation as such requires a copy-operation to construct the `FakeType<>`, so it might not be ideal for large types in a performance-critical application. There are some improvements to be made here also, but I didn't include those yet. – JorenHeit Oct 31 '14 at 09:27