带有模板参数的模板中的默认值 ( C++ )

发布于 2024-10-22 01:16:32 字数 1112 浏览 2 评论 0 原文

假设我有一个模板(称为ExampleTemplate),它接受两个参数:容器类型(例如列表、向量)和包含类型(例如float、bool 等)。由于容器实际上是模板,因此该模板有一个模板参数。这就是我必须写的:

#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容器,可移植性问题 - 例如“分配器”)?

Assume I have a template (called ExampleTemplate) that takes two arguments: a container type (e.g. list, vector) and a contained type (e.g. float, bool, etc). Since containers are in fact templates, this template has a template param. This is what I had to write:

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

You may ask what is the "allocator" thing about. Well, Initially, I tried the obvious thing...

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

...but I unfortunately found out that the default argument of the allocator...

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

...had to be explicitely "reserved" in the template declaration.
This, as you can see, makes the code uglier, and forces me to reproduce the default values of the template arguments (in this case, the allocator).

Which is BAD.

EDIT: The question is not about the specific problem of containers - it is about "Default values in templates with template arguments", and the above is just an example. Answers depending on the knowledge that STL containers have a "::value_type" are not what I am after. Think of the generic problem: if I need to use a template argument C in a template ExampleTemplate, then in the body of ExampleTemplate, do I have to reproduce the default arguments of C when I use it? If I have to, doesn't that introduce unnecessary repetition and other problems (in this case, where C is an STL container, portability issues - e.g. "allocator" )?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

蓝礼 2024-10-29 01:16:32

也许您更喜欢这样:

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

这使用“静态鸭子打字”。它也更加灵活,因为它不强制容器类型支持 STL 的分配器概念。


也许使用 类型特征 习惯用法可以给你一条出路:

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

有人想要使用 具有不符合 STL 的容器的 ForExamplePurposes 需要专门化 ValueTypeOf 特征类。

Perhaps you'd prefer this:

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

This uses "static duck typing". It is also a bit more flexible as it doesn't force the Container type to support STL's Allocator concept.


Perhaps using the type traits idiom can give you a way out:

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

Someone who wants to use ForExamplePurposes with a non-STL-compliant container would need to specialize the ValueTypeOf traits class.

人事已非 2024-10-29 01:16:32

我建议创建适配器。

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

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

编辑:尝试提供更多内容很好,但注定会失败,请查看各种扩展:

  • std: :vector: std::vector>
  • std::stack: std::stack>
  • std::set: std::set, std::allocator>

第二个是适配器,因此不采用分配器,第三个没有相同的数量。因此,您需要将责任放在用户身上。

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

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

Example<Vector, bool> example;

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

I would propose to create adapters.

Your class should be created with the exact level of personalization that is required by the class:

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

EDIT: attempting to provide more is nice, but doomed to fail, look at the various expansions:

  • std::vector<T>: std::vector<T, std::allocator<T>>
  • std::stack<T>: std::stack<T, std::deque<T>>
  • std::set<T>: std::set<T, std::less<T>, std::allocator<T>>

The second is an adapter, and so does not take an allocator, and the third does not have the same arity. You need therefore to put the onus on the user.

If a user wishes to use it with a type that does not respect the expressed arity, then the simplest way for him is to provide (locally) an adapter:

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

Example<Vector, bool> example;

I am wondering about the use of parameter packs (variadic templates) here... I don't know if declaring C as template <class...> C would do the trick or if the compiler would require a variadic class then.

错々过的事 2024-10-29 01:16:32

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

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

You have to give the full template signature, including default parameters, if you want to be able to use the template template parameter the usual way.

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

If you want to handle other containers that the one from the STL, you can delegate container construction to a helper.

// 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:
    ....
};
时光瘦了 2024-10-29 01:16:32

我认为,需要重现所有模板参数,甚至默认参数​​。请注意,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 { ... };

I think, it is required to reproduce all template parameters, even default. Note, that Standard itself does not use template template parameters for containter adaptors, and prefers to use regular template parameters:

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 { ... };
っ〆星空下的拥抱 2024-10-29 01:16:32

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


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

但我真的不认为这就是你要找的。您对容器所做的操作不太可能适用于任意容器,因为其中许多容器都会遇到使用多个默认参数(默认类型不明显)的问题。

The following code will allow you to do something like you're asking for. Of course, this won't work with standard containers, since this has to already be part of the template class that's being passed into the template.


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

But I don't really think this is what you're looking for. What you're doing with the containers is unlikely to be applicable to arbitrary containers as many of them will have the problem you're having with multiple default parameters with non-obvious types as defaults.

不乱于心 2024-10-29 01:16:32

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

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

然后,在类内部,用合理的参数替换这个虚拟参数。要替换类型,可以使用以下帮助器类:

// 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::向量; >,两个 T 都被替换,而不仅仅是第一个。对于多个嵌套层次结构也是如此。

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

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 的...刚刚意识到它可能会更好一点。

As the question exactly described the problem I had in my code (--I'm using Visual Studio 2015), I figured out an alternative solution which I wanted to share.

The idea is the following: instead of passing a template template parameter to the ExampleTemplate class template, one can also pass a normal typename which contains a type DummyType as dummy parameter, say std::vector<DummyType>.

Then, inside the class, one replace this dummy parameter by something reasonable. For replacement of the typethe following helper classes can be used:

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

Note the recursive step in replace_type, which takes care that types nested in other classes are replaced as well -- with this, for example, in std::vector<T, allocator<T> >, both T's are replaced and not only the first one. The same goes for more than one nesting hierarchy.

Next, you can use this in your ExampleTemplate-class,

struct DummyType {};

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

and call it via

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

Beside the not-that-nice syntac, this has the advantage that

  1. It works with any number of default template parameters -- for instance, consider the case with std::map in the example.

  2. There is no need to explicitly specify any default template parameters whatsoever.

  3. It can be easily extended to more dummy parameters (whereas then it probably should not be called by users ...).

By the way: Instead of the dummy type you can also use the std::placeholder's ... just realized that it might be a bit nicer.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文