【问题标题】:Default values in templates with template arguments ( C++ )带有模板参数的模板中的默认值 (C++)
【发布时间】:2011-07-15 04:11:48
【问题描述】:

假设我有一个带有两个参数的模板(称为 ExampleTemplate):一个容器类型(例如列表、向量)和一个包含类型(例如浮点数、布尔值等)。因为容器实际上是模板,所以这个模板有一个模板参数。这是我必须写的:

#include <vector>
#include <list>

using namespace std;

template < template <class,class> class C, typename T>
class ExampleTemplate {
    C<T,allocator<T> > items;
public:
    ....
};

main()
{
    ExampleTemplate<list,int> a;
    ExampleTemplate<vector,float> b;
}

您可能会问“分配器”是什么。好吧,最初,我尝试了显而易见的事情......

template < template <class> class C, typename T>
class ExampleTemplate {
    C<T> items;
};

...但不幸的是我发现分配器的默认参数...

   vector<T, Alloc>
   list<T, Alloc>
   etc

...必须在模板声明中明确“保留”。 如您所见,这使代码更丑陋,并迫使我重现模板参数(在本例中为分配器)的默认值。

这是不好

编辑:这个问题不是关于容器的具体问题——它是关于“模板参数的模板中的默认值”,上面只是一个例子。取决于 STL 容器具有“::value_type”的知识的答案不是我所追求的。想想一般的问题:如果我需要在模板ExampleTemplate 中使用模板参数C,那么在ExampleTemplate 的主体中,我使用时是否必须重现C 的默认参数?如果我不得不这样做,那不会引入不必要的重复和其他问题(在这种情况下,C 是 STL 容器,可移植性问题 - 例如“分配器”)?

【问题讨论】:

  • 更糟的是,你的代码不能在所有编译器上工作,因为标准库容器可能有(并且在某些实现中)更多具有标准值的模板参数.这段代码实际上是不可移植的。
  • 同意。我真希望我不必求助于宏……天哪,除了宏……
  • 如果您让我们更深入地了解您实际尝试做的事情,这可能有助于从您的非常具体的问题中展开。你所要求的是不可能的。
  • @Mike:“我所要求的是不可能的” - 如果我理解正确,您的意思是当我将模板参数 C 传递给模板(MyExample)时,在 MyExample 的正文中,我必须手动重现 C 的默认参数。这就是你的意思吗?
  • @Konrad,我想extra template arguments weren't allowed 正是因为这个用例带有模板模板参数。

标签: c++ templates


【解决方案1】:

也许你更喜欢这个:

#include <vector>
#include <list>

using namespace std;

template <class Container>
class ForExamplePurposes {
    typedef typename Container::value_type T;
    Container items;
public:
};

int main()
{
    ForExamplePurposes< list<int> > a;
    ForExamplePurposes< vector<float> > b;
}

这使用“静态duck typing”。它也更灵活一点,因为它不强制 Container 类型支持 STL 的 Allocator 概念。


也许使用type traits 成语可以给你一条出路:

#include <vector>
#include <list>

using namespace std;

struct MyFunkyContainer
{
    typedef int funky_type;
    // ... rest of custom container declaration
};

// General case assumes STL-compatible container
template <class Container>
struct ValueTypeOf
{
    typedef typename Container::value_type type;
};

// Specialization for MyFunkyContainer
template <>
struct ValueTypeOf<MyFunkyContainer>
{
    typedef MyFunkyContainer::funky_type type;
};


template <class Container>
class ForExamplePurposes {
    typedef typename ValueTypeOf<Container>::type T;
    Container items;
public:
};

int main()
{
    ForExamplePurposes< list<int> > a;
    ForExamplePurposes< vector<float> > b;
    ForExamplePurposes< MyFunkyContainer > c;
}

想要将 ForExamplePurposes 与不符合 STL 的容器一起使用的人需要专门化 ValueTypeOf 特征类。

【讨论】:

  • 这取决于 STL 容器可以通过 ::value_type 提供对包含类型的访问的知识。谢谢,但上面只是一个例子——想想一般问题,你没有这样的“快捷方式”,需要将模板传递给模板。如果不手动复制默认参数就没有办法吗?
  • @ttsiofras:仅供参考,标准要求所有 STL 容器定义 value_type 嵌套类型。
  • 同意,但同样,我的问题不是关于 STL 容器(这只是一个示例)。这是关于模板的模板参数。
  • @ttsiodras:我认为您需要考虑一个不同的示例并将其附加到您的问题中(将旧示例留在那里,这样人们就不会认为我疯了)。
  • @ttsiodras:我想到了另一个可能给你一条出路的技巧。查看更新的答案。
【解决方案2】:

我建议创建适配器。

您的班级应该按照班级所需的确切个性化级别创建:

template <template <class> C, template T>
class Example
{
  typedef T Type;
  typedef C<T> Container;
};

编辑:尝试提供更多很好,但注定要失败,看看各种扩展:

  • std::vector&lt;T&gt;: std::vector&lt;T, std::allocator&lt;T&gt;&gt;
  • std::stack&lt;T&gt;std::stack&lt;T, std::deque&lt;T&gt;&gt;
  • std::set&lt;T&gt;: std::set&lt;T, std::less&lt;T&gt;, std::allocator&lt;T&gt;&gt;

第二个是适配器,因此不带分配器,第三个没有相同的数量。因此,您需要将责任推给用户。

如果用户希望将其与不尊重表达的数量的类型一起使用,那么对他来说最简单的方法是提供(本地)适配器:

template <typename T>
using Vector = std::vector<T>; // C++0x

Example<Vector, bool> example;

我想知道这里参数包(可变参数模板)的使用...我不知道将 C 声明为 template &lt;class...&gt; C 是否可以解决问题,或者编译器是否需要可变参数类。

【讨论】:

  • 我一定遗漏了一些东西——Matthieu,我看不出你的第一个代码部分是如何编译的,因为如果我通过 Example,我会在 C 实例化。
  • @ttsiodras:这是一个例子 :) 确实第一个例子不能按原样使用,但问题更普遍。您无法猜测默认模板参数,例如,对于 stack,默认实例化将是 std::stack&lt;T, std::deque&lt;T&gt;&gt;,请注意第二个参数不是 std::allocator&lt;T&gt; 吗?因此,您需要要求用户提供适合的类,这是她的责任。第二部分展示了 C++0x 使用模板的别名轻松完成它。
  • @ttsiodras:我进行了编辑以使其更清晰,并揭露想要“漂亮”姿势的问题。令人讨厌的是它不能开箱即用,但它也让人想起指向函数和具有默认参数的函数的指针:您需要创建一个适配器(一个 thunk)来匹配签名。
【解决方案3】:

如果您希望能够以通常的方式使用模板模板参数,则必须提供完整的模板签名,包括默认参数。

template <typename T, template <class U, class V = allocator<U> > class C>
class ExampleTemplate {
    C<T> items;
public:
    ....
};

如果您想处理来自 STL 的其他容器,您可以将容器构造委托给助手。

// Other specialization failed. Instantiate a std::vector.
template <typename T, typename C>
struct make_container_
{
    typedef std::vector<T> result;
};

// STL containers
template <typename T, template <class U, class V = allocator<U> > class C>
struct make_container_<T,C>
{
    typedef C<T> result;
};

// Other specializations
...

template <typename T, typename C>
class ExampleTemplate {
    make_container_<T,C>::result items;
public:
    ....
};

【讨论】:

    【解决方案4】:

    我认为,需要重现所有模板参数,即使是默认参数。请注意,Standard 本身不使用容器适配器的模板模板参数,而是更喜欢使用常规模板参数:

    template < class T , class Container = deque <T > > class queue { ... };
    template < class T , class Container = vector <T>, class Compare = less < typename Container :: value_type > > class priority_queue { ... };
    

    【讨论】:

      【解决方案5】:

      以下代码将允许您执行您要求的操作。当然,这不适用于标准容器,因为它必须已经是传递到模板中的模板类的一部分。

      
      /* Allows you to create template classes that allow users to specify only some
       * of the default parameters, and some not.
       *
       * Example:
       *  template <typename A = use_default, typename B = use_default>
       *  class foo
       *  {
       *              typedef use_default_param<A, int> a_type;
       *              typedef use_default_param<B, double> b_type;
       *              ...
       *  };
       *
       *  foo<use_default, bool> x;
       *  foo<char, use_default> y;
       */
      
      struct use_default;
      
      template<class param, class default_type>
      struct default_param
      {
              typedef param type;
      };
      
      template<class default_type>
      struct default_param<use_default, default_type>
      {
              typedef default_type type;
      };
      

      但我真的不认为这就是你要找的东西。您对容器所做的事情不太可能适用于任意容器,因为其中许多容器都会遇到您遇到的问题,即使用多个默认参数并将非显而易见的类型作为默认值。

      【讨论】:

        【解决方案6】:

        由于问题准确描述了我在代码中遇到的问题(--我使用的是 Visual Studio 2015),我想出了一个我想分享的替代解决方案。

        想法如下:除了将模板模板参数传递给ExampleTemplate 类模板之外,还可以传递一个包含类型DummyType 作为虚拟参数的普通类型名,例如std::vector&lt;DummyType&gt;

        然后,在类内部,用一些合理的东西替换这个虚拟参数。为了替换类型,可以使用以下辅助类:

        // this is simply the replacement for a normal type:
        // it takes a type T, and possibly replaces it with ReplaceByType
        template<typename T, typename ReplaceWhatType, typename ReplaceByType>
        struct replace_type
        {
            using type = std::conditional_t<std::is_same<T, ReplaceWhatType>::value, ReplaceByType, T>;    
        };
        
        // this sets up the recursion, such that replacement also happens
        // in contained nested types
        // example: in "std::vector<T, allocator<T> >", both T's are replaced
        template<template<typename ...> class C, typename ... Args, typename ReplaceWhatType, typename ReplaceByType>
        struct replace_type<C<Args ...>, ReplaceWhatType, ReplaceByType>
        {
            using type = C<typename replace_type<Args, ReplaceWhatType, ReplaceByType>::type ...>;
        };
        
        // an alias for convenience
        template<typename ... Args>
        using replace_type_t = typename replace_type<Args ...>::type;
        

        注意replace_type 中的递归步骤,它注意替换嵌套在其他类中的类型——例如,在std::vector&lt;T, allocator&lt;T&gt; &gt; 中,两个T 都被替换了,而不仅仅是第一。对于多个嵌套层次结构也是如此。

        接下来,您可以在ExampleTemplate-class 中使用它,

        struct DummyType {};
        
        template <typename C, typename T>
        struct ExampleTemplate
        {
            replace_type_t<C, DummyType, T> items;
        };
        

        并通过调用它

        int main()
        {
            ExampleTemplate<std::vector<DummyType>, float> a;
            a.items.push_back(1.0);
            //a.items.push_back("Hello");  // prints an error message which shows that DummyType is replaced correctly
        
            ExampleTemplate<std::list<DummyType>, float> b;
            b.items.push_back(1.0);
            //b.items.push_back("Hello");  // prints an error message which shows that DummyType is replaced correctly
        
            ExampleTemplate<std::map<int, DummyType>, float> c;
            c.items[0]=1.0;
            //c.items[0]="Hello";          // prints an error message which shows that DummyType is replaced correctly
        }
        

        DEMO

        除了不那么好的语法之外,这还有一个优点

        1. 它适用于任意数量的默认模板参数——例如,考虑示例中std::map 的情况。

        2. 无需显式指定任何默认模板参数。

        3. 它可以很容易地扩展到更多的虚拟参数(而用户可能不应该调用它......)。

        顺便说一句:除了虚拟类型,您还可以使用std::placeholder's ...刚刚意识到它可能会更好一点。

        【讨论】:

          猜你喜欢
          • 2011-08-27
          • 1970-01-01
          • 2018-07-16
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-05-09
          • 1970-01-01
          相关资源
          最近更新 更多