Question

Consider the following (invalid) code sample:

// a: base template for function with only one parameter
template<typename T>
void f(T t) { }

// b: base tempalte for function with two parameters
template<typename T1, typename T2>
void f(T1 t1, T2 t2) { }

// c: specialization of a for T = int
template<>
void f<int>(int i) { }

// d: specialization for b with T1 = int - INVALID
template<typename T2>
void f<int, T2>(int i, T2 t2) { }

int main() {
    f(true);        // should call a
    f(true, false); // should call b

    f(1);           // should call c
    f(1, false);    // should call d
}

I've read this walk-through on why, in general, partial function template specializations won't work, and I think I understand the basic reasoning: there are cases where function template specializations and overloading would make certain calls ambiguous (there are good examples in the article).

However, is there a reason why this specific example wouldn't work, other than "the standard says it shouldn't"? Does anything change if I can guarantee (e.g. with a static_assert) that the base template is never instantiated? Is there any other way to achieve the same effect?


What I actually want to achieve is to create an extendable factory method

template<typename T>
T create();

which also has a few overloads taking input parameters, e.g.

template<typename T, typename TIn>
T create(TIn in);
template<typename T, typename TIn1, typename TIn2>
T create(TIn1 in1, TIn2 in2);

In order to ensure that all necessary factory methods are present, I use static_assert in the function base templates, so that a compiler error is generated if the create method is called with template arguments for which no specialization has been provided.

I want these to be function templates rather than class templates because there will be quite a lot of them, and they will all use input from the same struct hierarchy, so instantiating 10 factories instead of one comes with some overhead that I'd like to avoid (not considering the fact that the code gets much easier to understand this way, if I can just get it to work...).

Is there a way to get around the problem outlined in the first half of this post, in order to achieve what I've tried to get at with the second half?


In response to iavr:

I could do this with plain overloading, which would (given the templates above) give something like

template<typename TIn2>
A create(bool, TIn2);
template<typename TIn2>
A create(int, TIn2);

if I need two different partial specializations with T = A, TIn1 specified and TIn2 still unspecified. This is a problem, since I have some cases (which are really text-book cases for meta-programming and templates) where I know that, for example, one of the arguments will be a std::string, and the other will be of some type that has a property fields and a property grids, which are of types std::vector<field> and std::vector<grid> respectively. I don't know all the types that will ever be supplied as the second argument - I know for sure that there will be more of them than the ones I currently have implemented - but the implementation of the method will be exactly the same.

While writing up this update, I think I've figured out a way to redesign the implementations so that there is no need for the partial specialization - basically, I do the following to cover the case outlined above:

template<>
A create<A, std::vector<field>, std::vector<grid>>(std::vector<field> fs, std::vector<grid> gs);

and then I have to change the calling signature slightly, but that's OK.

Was it helpful?

Solution 3

If you don't want overloading, and want to be able to specialize from a separate file, then I think you should base it on the solution on the link from your question. It involves making a static method on a class that you specialize. From my reading of the question, you're only interested in specializing on the T, not on the number of arguments, which you intend to forward. In C++11, you can do the following:

#include <iostream>
#include <utility>
using namespace std;


template<typename T>
struct factory_impl;
// Left unspecified for now (which causes compliation failure if
// not later specialized

template<typename T, typename... Args>
T create(Args&&... args)
{
   return factory_impl<T>::create(std::forward<Args>(args)...);
}


// Note, this can be specified in a header in another translation
// unit. The only requirement is that the specialization
// be defined prior to calling create with the correct value
// of T
template<>
struct factory_impl<int>
{
   // int can be constructed with 0 arguments or 1 argument
   static int create(int src = 0)
   {
     return src;
   }
};

int main(int argc, char** argv)
{
   int i = create<int>();
   int j = create<int>(5);
   // double d = create<double>(); // Fails to compile

   std::cout << i << " " << j << std::endl;
   return 0;
}

Live example http://ideone.com/7a3uRZ

Edit: In response to your question, you could also make create a member function of a class, and pass along some of that data with the call or take action before or after

struct MyFactory
{

   template<typename T, typename... Args>
   T create(Args&&... args)
   {
      T ret = factory_impl<T>::create(data, std::forward<Args>(args)...);
      // do something with ret
      return ret;
   }

   Foo data; // Example
};

OTHER TIPS

I share your concerns that maybe in this particular case there would be no problem having function template partial specializations, but then again, that's the way it is, so what would be your problem using plain overloading?

// a: base template for function with only one parameter
template<typename T>
void f(T t) { }

// b: base template for function with two parameters
template<typename T1, typename T2>
void f(T1 t1, T2 t2) { }

// c: specialization of a for T = int
void f(int i) { }

// d: specialization for b with T1 = int
template<typename T2>
void f(int i, T2 t2) { }

This also takes less typing and I get this is why you don't want to use function objects (which would have partial specialization).

Here is a simple workaround using a class template specialization:

template <typename, typename...>
struct Creator;

template <typename T, typename TIn>
struct Creator<T, TIn>
{
    T call(TIn in)
    {
        // ...
    }
};

template<typename T, typename TIn1, typename TIn2>
struct Creator<T, TIn1, TIn2>
{
    T call(TIn1 in1, TIn2 in2)
    {
        // ...
    }
};

template <typename R, typename... Arguments>
R Create(Arguments&&... arguments)
{
    return Creator<R, Arguments...>::call(std::forward<Arguments>(arguments)...);
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top