开发者

Template classes with specialised constructors

Consider the following contrived example of a templated array definition:

template <typename t, unsigned int n> class TBase
{
protected:
    t m_Data[n];

    //...
};

template <typename t, unsigned int n> class TDerived : public TBase<t, n>
{
    TDerived()
    {
    }
};

I can specialize this type to provide a non-default constructor for an array of length 2 as follows:

template <typename t> class TDerived<t, 2> : public TBase<t, 2>
{
public:
    TDerived(const t& x0, const t& x1)
    {
        m_Data[0] = x0;
        m_Data[1] = x1;
    }
};

int main()
{
    TDerived<float, 2> Array2D_A(2.0f, 3.0f); //uses specialised constructor
    TDerived<float, 3> Arra开发者_StackOverflowy3D_A;             //uses default constructor

    return 0;
}

Is there some other way I can create a class that has different constructor options constrained against template parameters at compile-time without the requirement for a complete class specialisation for each variation?

In other words, is there some way I can have specialised constructors in the TBase class without the need for the intermediary step of creating TDerived whilst preserving the functionality of TBase?


I think deriving your class from a base class is not relevant to the question here, that's a mere implementation detail. What you really seem to be after is if there's a way to partially specialize member functions, like the constructor. Do you want something like this?

template <typename T, int N> class Foo
{
    Foo(); // general
    template <typename U> Foo<U, 2>(); // specialized, NOT REAL CODE
};

This doesn't work. You always have to specialize the entire class. The reason is simple: You have to know the full type of the class first before you even know which member functions exist. Consider the following simple situation:

template <typename  T> class Bar
{
  void somefunction(const T&);
};

template <> class Bar<int>
{
  double baz(char, int);
};

Now Bar<T>::somefunction() depends on T, but the function only exists when T is not int, because Bar<int> is an entirely different class.

Or consider even another specialization template <> class Bar<double> : public Zip {}; -- even the polymorphic nature of a class can be entirely different in a specialization!

So the only way you can provide specializations new declarations of members, including constructors, is by specializing the entire class. (You can specialize the definition of existing functions, see @Alf's answer.)


There are basically two options I see for this:

  • Use a variadic function for construction (ie. "..." notation), you can use the value n inside that function to get your arguments from the stack. However, the compiler will not check at compile time if the user provides the correct number of arguments.

  • Use some serious template magic to allow a call chaning initialization, that would look like this: vector(2.0f)(3.0f). You can actually build something that at least ensures the user does not provide too many arguments here. However tha mechanism is a little more involved, I can assemble an example if you want.


You can always specialize a member, e.g.

#include <stdio.h>

template< class Type >
struct Foo
{
    void bar() const
    { printf( "Single's bar.\n" ); }
};

template<>
void Foo< double >::bar() const
{ printf( "double's bar.\n" ); }

int main()
{
    Foo<int>().bar();
    Foo<double>().bar();
}

But you want effectively different signatures, so it's not directly a case of specializing a member.

One way forward is then to declare a constructor with a single argument, of a type dependent on the template parameters.

Then you can specialize that, as you want.

Cheers & hth.,


Since constructor is a function, you need to fully specialize the containing class to address your specific problem. No way out.

However, functions cannot be partially specialized (in all compilers). So suppose if you know that you need n = 2 when t = int or double then following is one alternative.

template<>
TDerived<int,2>::TDerived()
{
  //...
}
template<>
TDerived<double,2>::TDerived()
{
  //...
}

and so on.

[Note: If you use MSVC, then I think it supports partial specialization; in that case you can try:

template<typename t>
TDerived<t,2>::TDerived()
{
  //...
}

though, I am not sure enough for that.]


You could give the most common definitions in the non-specialized class and static_assert (BOOST_STATIC_ASSERT for non C++0x) on the array length. This could be considered a hack but is a simple solution to your problem and safe.

template<typename T, unsigned int n>
struct Foo {
  Foo(const T& x) { static_assert(n == 1, "Mooh!"); }
  Foo(const T& x1, const T& x2) { static_assert(n == 2, "Mooh!"); }
};

The "evil" way would be variadic arguments.

template<typename T, unsigned int n>
struct Foo {
  Foo(...) { 
    va_list ap;
    va_start(ap, n);
    for(int j=0; j < n; ++j)
      bork[j] = va_arg(ap, T);
    va_end(ap);
  }
};

Then there is also C++0x and the good old make_something trick which is more difficult then one would think.

template<typename... T, unsigned int n>
Foo<T, n> make_foo(T&&...) {
  // figure out the common_type of the argument list
  // to our Foo object with setters or as a friend straight to the internals
  Foo< std::common_type< T... >::type, sizeof(T) > foo;
  // recursive magic to pick the list apart and assign 
  // ...
  return foo;
}
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜