仅接受某些类型的 C++ 模板
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/874298/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
C++ templates that accept only certain types
提问by mgamer
In Java you can define generic class that accept only types that extends class of your choice, eg:
在 Java 中,您可以定义仅接受扩展您选择的类的类型的泛型类,例如:
public class ObservableList<T extends List> {
...
}
This is done using "extends" keyword.
这是使用“extends”关键字完成的。
Is there some simple equivalent to this keyword in C++?
在 C++ 中是否有一些简单的等价于 this 关键字?
采纳答案by j_random_hacker
I suggest using Boost's static assertfeature in concert with is_base_of
from the Boost Type Traits library:
我建议将 Boost 的静态断言功能与is_base_of
来自 Boost Type Traits 库的功能结合使用:
template<typename T>
class ObservableList {
BOOST_STATIC_ASSERT((is_base_of<List, T>::value)); //Yes, the double parentheses are needed, otherwise the comma will be seen as macro argument separator
...
};
In some other, simpler cases, you can simply forward-declare a global template, but only define (explicitly or partially specialise) it for the valid types:
在其他一些更简单的情况下,您可以简单地向前声明一个全局模板,但只为有效类型定义(显式或部分专门化)它:
template<typename T> class my_template; // Declare, but don't define
// int is a valid type
template<> class my_template<int> {
...
};
// All pointer types are valid
template<typename T> class my_template<T*> {
...
};
// All other types are invalid, and will cause linker error messages.
[Minor EDIT 6/12/2013: Using a declared-but-not-defined template will result in linker, not compiler, error messages.]
[次要编辑 6/12/2013:使用声明但未定义的模板将导致链接器,而不是编译器,错误消息。]
回答by Rapptz
This typically is unwarranted in C++, as other answers here have noted. In C++ we tend to define generic types based on other constraints other than "inherits from this class". If you really wanted to do that, it's quite easy to do in C++11 and <type_traits>
:
正如此处的其他答案所指出的那样,这在 C++ 中通常是没有根据的。在 C++ 中,我们倾向于根据“继承自此类”以外的其他约束来定义泛型类型。如果你真的想这样做,在 C++11 中很容易做到,并且<type_traits>
:
#include <type_traits>
template<typename T>
class observable_list {
static_assert(std::is_base_of<list, T>::value, "T must inherit from list");
// code here..
};
This breaks a lot of the concepts that people expect in C++ though. It's better to use tricks like defining your own traits. For example, maybe observable_list
wants to accept any type of container that has the typedefs const_iterator
and a begin
and end
member function that returns const_iterator
. If you restrict this to classes that inherit from list
then a user who has their own type that doesn't inherit from list
but provides these member functions and typedefs would be unable to use your observable_list
.
但是,这打破了人们在 C++ 中期望的许多概念。最好使用诸如定义自己的特征之类的技巧。例如,也许observable_list
要接受任何类型的容器具有类型定义const_iterator
和一个begin
和end
成员函数返回const_iterator
。如果您将其限制为继承自的类,list
那么拥有自己的类型但不继承自list
但提供这些成员函数和 typedef 的用户将无法使用您的observable_list
.
There are two solutions to this issue, one of them is to not constrain anything and rely on duck typing. A big con to this solution is that it involves a massive amount of errors that can be hard for users to grok. Another solution is to define traits to constrain the type provided to meet the interface requirements. The big con for this solution is that involves extra writing which can be seen as annoying. However, the positive side is that you will be able to write your own error messages a la static_assert
.
这个问题有两种解决方案,其中一种是不限制任何东西并依赖鸭子类型。这个解决方案的一个大缺点是它涉及大量用户难以理解的错误。另一种解决方案是定义特征来约束提供的类型以满足接口要求。这个解决方案的最大缺点是涉及额外的写作,这可能会很烦人。但是,积极的一面是您将能够编写自己的错误消息 la static_assert
。
For completeness, the solution to the example above is given:
为完整起见,给出了上述示例的解决方案:
#include <type_traits>
template<typename...>
struct void_ {
using type = void;
};
template<typename... Args>
using Void = typename void_<Args...>::type;
template<typename T, typename = void>
struct has_const_iterator : std::false_type {};
template<typename T>
struct has_const_iterator<T, Void<typename T::const_iterator>> : std::true_type {};
struct has_begin_end_impl {
template<typename T, typename Begin = decltype(std::declval<const T&>().begin()),
typename End = decltype(std::declval<const T&>().end())>
static std::true_type test(int);
template<typename...>
static std::false_type test(...);
};
template<typename T>
struct has_begin_end : decltype(has_begin_end_impl::test<T>(0)) {};
template<typename T>
class observable_list {
static_assert(has_const_iterator<T>::value, "Must have a const_iterator typedef");
static_assert(has_begin_end<T>::value, "Must have begin and end member functions");
// code here...
};
There are a lot of concepts shown in the example above that showcase C++11's features. Some search terms for the curious are variadic templates, SFINAE, expression SFINAE, and type traits.
上面的例子中有很多概念展示了 C++11 的特性。一些好奇的搜索词是可变参数模板、SFINAE、表达式 SFINAE 和类型特征。
回答by jalf
The simple solution, which no one have mentioned yet, is to just ignore the problem. If I try to use an int
as a template type in a function template that expects a container class such as vector or list, then I will get a compile error. Crude and simple, but it solves the problem. The compiler will try to use the type you specify, and if that fails, it generates a compile error.
没有人提到过的简单解决方案就是忽略这个问题。如果我尝试int
在需要容器类(例如 vector 或 list)的函数模板中使用 an作为模板类型,那么我将收到编译错误。粗鲁和简单,但它解决了问题。编译器将尝试使用您指定的类型,如果失败,则会生成编译错误。
The only problem with that is that the error messages you get are going to be tricky to read. It is nevertheless a very common way to do this. The standard library is full of function or class templates that expect certain behavior from the template type, and do nothing to check that the types used are valid.
唯一的问题是您收到的错误消息将难以阅读。尽管如此,这是一种非常常见的方法。标准库充满了函数或类模板,它们期望模板类型的某些行为,并且不检查所使用的类型是否有效。
If you want nicer error messages (or if you want to catch cases that wouldn't produce a compiler error, but still don't make sense) you can, depending on how complex you want to make it, use either Boost's static assert or the Boost concept_check library.
如果您想要更好的错误消息(或者如果您想捕获不会产生编译器错误但仍然没有意义的情况),您可以根据您想要制作它的复杂程度,使用 Boost 的静态断言或Boost concept_check 库。
With an up-to-date compiler you have a built_in static_assert
, which could be used instead.
使用最新的编译器,您可以使用 built_instatic_assert
来代替。
回答by Barry Carr
As far as I know this isn't currently possible in C++. However, there are plans to add a feature called "concepts" in the new C++0x standard that provide the functionality that you're looking for. This Wikipedia articleabout C++ Concepts will explain it in more detail.
据我所知,这在 C++ 中目前是不可能的。但是,有计划在新的 C++0x 标准中添加一个称为“概念”的特性,以提供您正在寻找的功能。这Wikipedia文章关于C ++的概念进行详细解释。
I know this doesn't fix your immediate problem but there are some C++ compilers that have already started to add features from the new standard, so it might be possible to find a compiler that has already implemented the concepts feature.
我知道这并不能解决您当前的问题,但是有一些 C++ 编译器已经开始添加新标准中的功能,因此可能会找到一个已经实现了概念功能的编译器。
回答by firda
We can use std::is_base_of
and std::enable_if
:
(static_assert
can be removed, the above classes can be custom-implemented or used from boostif we cannot reference type_traits
)
我们可以用std::is_base_of
和std::enable_if
:
(static_assert
可以被删除,上面的类可以定制实现或者使用升压,如果我们不能参考type_traits
)
#include <type_traits>
#include <list>
class Base {};
class Derived: public Base {};
#if 0 // wrapper
template <class T> class MyClass /* where T:Base */ {
private:
static_assert(std::is_base_of<Base, T>::value, "T is not derived from Base");
typename std::enable_if<std::is_base_of<Base, T>::value, T>::type inner;
};
#elif 0 // base class
template <class T> class MyClass: /* where T:Base */
protected std::enable_if<std::is_base_of<Base, T>::value, T>::type {
private:
static_assert(std::is_base_of<Base, T>::value, "T is not derived from Base");
};
#elif 1 // list-of
template <class T> class MyClass /* where T:list<Base> */ {
static_assert(std::is_base_of<Base, typename T::value_type>::value , "T::value_type is not derived from Base");
typedef typename std::enable_if<std::is_base_of<Base, typename T::value_type>::value, T>::type base;
typedef typename std::enable_if<std::is_base_of<Base, typename T::value_type>::value, T>::type::value_type value_type;
};
#endif
int main() {
#if 0 // wrapper or base-class
MyClass<Derived> derived;
MyClass<Base> base;
// error:
MyClass<int> wrong;
#elif 1 // list-of
MyClass<std::list<Derived>> derived;
MyClass<std::list<Base>> base;
// error:
MyClass<std::list<int>> wrong;
#endif
// all of the static_asserts if not commented out
// or "error: no type named ‘type' in ‘struct std::enable_if<false, ...>' pointing to:
// 1. inner
// 2. MyClass
// 3. base + value_type
}
回答by Alice
I think all prior answers have lost sight of the forest for the trees.
我认为所有先前的答案都只见树木不见森林。
Java generics are not the same as templates; they use type erasure, which is a dynamic technique, rather than compile time polymorphism, which is static technique. It should be obvious why these two very different tactics do not gel well.
Java 泛型与模板不同。他们使用类型擦除,这是一种动态技术,而不是编译时多态,这是一种静态技术。为什么这两种截然不同的策略不能很好地融合在一起,这应该是显而易见的。
Rather than attempt to use a compile time construct to simulate a run time one, let's look at what extends
actually does: according to Stack Overflowand Wikipedia, extends is used to indicate subclassing.
与其尝试使用编译时构造来模拟运行时构造,不如让我们看看extends
实际做了什么:根据 Stack Overflow和Wikipedia, extends 用于指示子类化。
C++ also supports subclassing.
C++ 还支持子类化。
You also show a container class, which is using type erasure in the form of a generic, and extends to perform a type check. In C++, you have to do the type erasure machinery yourself, which is simple: make a pointer to the superclass.
您还展示了一个容器类,它以泛型的形式使用类型擦除,并扩展以执行类型检查。在 C++ 中,你必须自己做类型擦除机制,这很简单:创建一个指向超类的指针。
Let's wrap it into a typedef, to make it easier to use, rather than make a whole class, et voila:
让我们把它包装成一个 typedef,让它更容易使用,而不是制作一个完整的类,等等:
typedef std::list<superclass*> subclasses_of_superclass_only_list;
typedef std::list<superclass*> subclasses_of_superclass_only_list;
For example:
例如:
class Shape { };
class Triangle : public Shape { };
typedef std::list<Shape*> only_shapes_list;
only_shapes_list shapes;
shapes.push_back(new Triangle()); // Works, triangle is kind of shape
shapes.push_back(new int(30)); // Error, int's are not shapes
Now, it seems List is an interface, representing a sort of collection. An interface in C++ would merely be an abstract class, that is, a class that implements nothing but pure virtual methods. Using this method, you could easily implement your java example in C++, without any Concepts or template specializations. It would also perform as slow as Java style generics due to the virtual table look ups, but this can often be an acceptable loss.
现在,List 似乎是一个接口,代表一种集合。C++ 中的接口只是一个抽象类,也就是说,一个只实现纯虚方法的类。使用这种方法,您可以轻松地在 C++ 中实现您的 java 示例,而无需任何概念或模板特化。由于虚拟表查找,它的执行速度也与 Java 风格的泛型一样慢,但这通常是可以接受的损失。
回答by nh_
An equivalent that only accepts types T derived from type List looks like
仅接受从类型 List 派生的类型 T 的等效项看起来像
template<typename T,
typename std::enable_if<std::is_base_of<List, T>::value>::type* = nullptr>
class ObservableList
{
// ...
};
回答by catphive
Executive summary: Don't do that.
执行摘要:不要那样做。
j_random_hacker's answer tells you howto do this. However, I would also like to point out that you should notdo this. The whole point of templates is that they can accept any compatible type, and Java style type constraints break that.
j_random_hacker 的回答告诉你如何做到这一点。但是,我也想指出你不应该这样做。模板的全部意义在于它们可以接受任何兼容的类型,而 Java 风格的类型约束打破了这一点。
Java's type constraints are a bug not a feature. They are there because Java does type erasure on generics, so Java can't figure out how to call methods based on the value of type parameters alone.
Java 的类型约束是一个错误而不是一个特性。它们存在是因为 Java 确实对泛型进行了类型擦除,因此 Java 无法弄清楚如何仅根据类型参数的值来调用方法。
C++ on the other hand has no such restriction. Template parameter types can be any type compatible with the operations they are used with. There doesn't have to be a common base class. This is similar to Python's "Duck Typing," but done at compile time.
另一方面,C++ 没有这样的限制。模板参数类型可以是与它们所使用的操作兼容的任何类型。不必有一个共同的基类。这类似于 Python 的“Duck Typing”,但在编译时完成。
A simple example showing the power of templates:
一个简单的例子展示了模板的力量:
// Sum a vector of some type.
// Example:
// int total = sum({1,2,3,4,5});
template <typename T>
T sum(const vector<T>& vec) {
T total = T();
for (const T& x : vec) {
total += x;
}
return total;
}
This sum function can sum a vector of any type that support the correct operations. It works with both primitives like int/long/float/double, and user defined numeric types that overload the += operator. Heck, you can even use this function to join strings, since they support +=.
此 sum 函数可以对支持正确运算的任何类型的向量求和。它适用于 int/long/float/double 等原语和用户定义的重载 += 运算符的数字类型。哎呀,你甚至可以使用这个函数来连接字符串,因为它们支持 +=。
No boxing/unboxing of primitives is necessary.
不需要对原语进行装箱/拆箱。
Note that it also constructs new instances of T using T(). This is trivial in C++ using implicit interfaces, but not really possible in Java with type constraints.
请注意,它还使用 T() 构造了 T 的新实例。这在使用隐式接口的 C++ 中是微不足道的,但在具有类型约束的 Java 中实际上不可能。
While C++ templates don't have explicit type constraints, they are still type safe, and will not compile with code that does not support the correct operations.
尽管 C++ 模板没有显式类型约束,但它们仍然是类型安全的,并且不会使用不支持正确操作的代码进行编译。
回答by macbirdie
That's not possible in plain C++, but you can verify template parameters at compile-time through Concept Checking, e.g. using Boost's BCCL.
这在普通 C++ 中是不可能的,但您可以在编译时通过概念检查验证模板参数,例如使用Boost 的 BCCL。
As of C++20, concepts are becoming an official featureof the language.
从 C++20 开始,概念正在成为该语言的官方特性。
回答by Stuart
class Base
{
struct FooSecurity{};
};
template<class Type>
class Foo
{
typename Type::FooSecurity If_You_Are_Reading_This_You_Tried_To_Create_An_Instance_Of_Foo_For_An_Invalid_Type;
};
Make sure derived classes inherit the FooSecurity structure and the compiler will get upset in all the right places.
确保派生类继承 FooSecurity 结构,编译器会在所有正确的地方感到不安。