【问题标题】:Template classes with specialised constructors具有专门构造函数的模板类
【发布时间】:2011-08-11 13:12:22
【问题描述】:

考虑以下人为设计的模板化数组定义示例:

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()
    {
    }
};

我可以专门为这个类型提供一个长度为 2 的数组的非默认构造函数,如下所示:

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> Array3D_A;             //uses default constructor

    return 0;
}

是否有其他方法可以创建一个类,该类具有不同的构造函数选项,在编译时受模板参数约束,而无需为每个变体提供完整的类专业化?

换句话说,有没有什么方法可以在TBase 类中使用专门的构造函数,而无需创建TDerived 的中间步骤,同时保留TBase 的功能?

【问题讨论】:

  • 您使用的是什么编译器和版本,是否支持C++0x?
  • @David Rodríguez - dribeas 我在 MSVC 2008/2010 之间切换,所以是和否。
  • 为什么不把所有的构造函数都放进去呢?除非被使用,否则它们不会被实例化。

标签: c++ templates constructor metaprogramming


【解决方案1】:

我认为从基类派生您的类与这里的问题无关,这仅仅是实现细节。您真正想要的是是否有一种方法可以部分专门化成员函数,例如构造函数。你想要这样的东西吗?

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

这不起作用。你总是必须专攻整个班级。原因很简单:您必须首先了解类的完整类型,然后才能知道存在哪些成员函数。考虑以下简单情况:

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

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

现在Bar&lt;T&gt;::somefunction() 依赖于T,但该函数仅在T 不是int存在,因为Bar&lt;int&gt; 是一个完全不同的类。

或者考虑另一个特化template &lt;&gt; class Bar&lt;double&gt; : public Zip {};——甚至一个类的多态性质在特化中也可能完全不同!

因此,您可以提供 specializations 成员的新声明(包括构造函数)的唯一方法是专门化整个类。 (您可以专门化现有函数的定义,请参阅@Alf 的回答。)

【讨论】:

  • -1 “提供成员特化(包括构造函数)的唯一方法是特化整个类。”不正确。
  • @Alf:嗯,好的,如果你有成员模板,你可以单独专门化它们。但我的意思是“在类参数上专门化成员”。怎么说呢?
  • 您可以专门化成员。例如。看我的回答。 :-)
  • 我明白了,但这是不同的,因为您没有更改签名,所以您正在专门化 定义。在我的示例中,您不能专门化 template&lt;&gt; Bar&lt;int&gt;::somefunction(const int&amp;),因为它不存在。这应该是精确的......
  • 好吧,我误会你了。并通过修改后的文本,我删除了反对票。 :-) 好吧,除了它已经被删除了,我不明白。呵呵。
【解决方案2】:

我看到的基本上有两种选择:

  • 使用可变参数函数进行构造(即“...”表示法),您可以使用该函数中的值 n 从堆栈中获取参数。但是,编译器不会在编译时检查用户是否提供了正确数量的参数。

  • 使用一些严肃的模板魔法来允许调用 chaning 初始化,看起来像这样:vector(2.0f)(3.0f)。您实际上可以构建至少确保用户不会在此处提供太多参数的东西。不过这个机制有点复杂,如果你愿意,我可以组装一个例子。

【讨论】:

  • 如果我能提供帮助,我根本不想接近可变参数!我很想知道您所说的使用模板向导调用链接初始化是什么意思......
  • 大致是这样的:创建一个模板类,它将接受向量中元素的数量作为模板参数 N。但是,让这个类只包含这些元素之一,并让它派生自一个少一个元素的类。专门用于 N=1 元素以停止继承链。像这样定义一个方法:Vector&lt;N-1&gt;&amp; operator()(double e) { element=e; return (Vector&lt;N-1&gt;&amp;)*this; },在 N=1 的特化中,有这个返回 void 或其他没有定义 operator() 的东西。
【解决方案3】:

你总是可以专门化一个成员,例如

#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();
}

但是你想要有效地不同的签名,所以这不是专门化成员的直接情况。

然后一种方法是声明一个带有单个参数的构造函数,其类型取决于模板参数。

然后,您可以根据需要进行专业化。

干杯,

【讨论】:

  • 一个重要说明。在 OP 的问题中有 2 个模板参数,所以我们需要知道这两个参数才能对其进行特殊化。我已经在我的回答中记录了这一点(可能是 MSVC 支持部分专业化)。
  • @Alf P. Steinbach @iammilind 虽然这是一个潜在的解决方案,但理想情况下,我希望对构造函数进行部分专业化——例如,通过指定长度而不是类型 TBase(const t&amp; x0){} , 部分特化如下template &lt;typename t&gt; TBase&lt;t, 1&gt;::TBase (const t&amp; x0) -- 但看起来这是不可能的。
  • @Munro,对于函数,标准不允许部分特化。因此,您可以尝试不同的类型,正如我在回答中提到的那样(如果没有那么多潜在类型)。
【解决方案4】:

由于构造函数是一个函数,您需要完全专门化包含类来解决您的特定问题。没有出路。

但是,函数不能部分特化(在所有编译器中)。所以假设如果你知道你需要n = 2t = int or double 那么下面是一种选择。

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

等等。

[注:如果你使用MSVC,那么我认为它支持偏特化;在这种情况下,您可以尝试:

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

不过,我对此不太确定。]

【讨论】:

  • 如果你再看看我原来的问题,我已经在使用部分模板专业化来创建我的专门构造函数,所以我不太确定你的建议超出了已经存在的内容。我正在使用 MSVC。
  • 对不起,我重读了你的帖子,我想我最初误解了。你是对的,我可以专门化构造函数,但显然不允许构造函数(或成员函数)的部分专门化。我没有意识到这一点!
  • @Munro,您正在对完整的class 使用部分专业化。我的意思是,如果您只想拥有&lt;t,2&gt; 的构造函数,那么您可以简单地专门化构造函数。你不必专攻全班。在 MSVC 中,我知道它们允许方法的部分特化;如果这不可能,那么您可以选择我在主要答案中的建议,您知道t 将是int,double,... 然后专门&lt;int,2&gt;, &lt;double,2&gt;
【解决方案5】:

您可以在非专业类中给出最常见的定义,并在数组长度上给出static_assert(非 C++0x 的 BOOST_STATIC_ASSERT)。这可能被认为是一种 hack,但它是解决您的问题的简单方法且安全。

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!"); }
};

“邪恶”的方式是可变参数。

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);
  }
};

还有 C++0x 和古老的 make_something 技巧,这比人们想象的要难。

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;
}

【讨论】:

  • 您的简单解决方案是我最初开始的方式(尽管使用非静态断言,但最初我进行了运行时检查),这就是我开始寻找替代方案的原因。我什至不会靠近你的“邪恶”路线!您的 c++0x 可变参数模板解决方案看起来很有趣,尽管我不熟悉语法(我还没有深入研究 c++0x 的奥秘和魔力)。
  • @Munro:我的make_something 确实需要一些调整,而common_type 部分有点脆弱。我可以将 Boost.PreProcessor 宏组合在一起,以使简单的方法不那么冗长。
猜你喜欢
  • 2016-07-02
  • 2015-03-12
  • 1970-01-01
  • 1970-01-01
  • 2021-11-11
  • 1970-01-01
  • 2022-12-13
  • 2019-10-17
相关资源
最近更新 更多