在 Fluent C++ 上,我们已经考虑过通过引用传递强类型,并意识到这不是一件简单的事情。要了解原因,我建议您阅读 上一篇文章,以便我们保持一致。
在我们之前的尝试中,我们创建了下面的类,它用来表示使用引用的强类型:
template<typename T, typename Parameter>
class NamedTypeRef
{
public:
explicit NamedTypeRef(T& t) : t_(std::ref(t)){}
T& get() {return t_.get();}
T const& get() const {return t_.get();}
private:
std::reference_wrapper<T> t_;
};
它可以使用下面的方式进行实例化:
using FirstNameRef = NamedTypeRef<std::string, struct FirstNameRefParameter>;
这很好用。但是有个缺点,那就是创建一个新组件,而不是我们在 C++ 中表示强类型的中心组件:NamedType。
在我将这项工作展示给不同的人之后,我得到的反馈和建议将我引向了另一个方向。结果是我们实际上可以通过 使用 NamedType 类本身 来表示引用、强类型。让我告诉你怎么做。
强类型引用
表现引用强类型的一种非常简单的方法是使用 NamedType 包装,用于在任何类型的引用上添加强类型:
using FirstNameRef = NamedType<std::string&, struct FirstNameRefParameter>;
很简单,是吧!
但是这个没法编译。
不完全的 NamedType
编译时错误提及了 NamedType 的构造函数,这是 NamedType 的定义:
template <typename T, typename Parameter>
class NamedType
{
public:
explicit NamedType(T const& value) : value_(value) {}
explicit NamedType(T&& value) : value_(value) {}
T& get() { return value_; }
T const& get() const {return value_; }
private:
T value_;
};
当 T 是引用时,假设其为 U&,这个引用导致实例化模板时的这些问题:
在第一个构造函数中,T const& 变为了 U& const&。最终成为了 U&。
在第二个构造函数中,T&& 变为了 U& &&。最终成为了 U&。
如果你对引用折叠不熟悉,https://channel9.msdn.com/Shows/Going+Deep/Cpp-and-Beyond-2012-Scott-Meyers-Universal-References-in-Cpp11[来自 Scott Meyers 的精彩演讲] 会告诉你所有理解上面两行需要的内容。
不管怎样。本质原因是两个构造函数的参数是一样的,从而导致无法编译。
使其兼容
一个简单的方法是 如果 T 是一个引用,则删除具右值引用的参数的构造函数 。因为移动一个引用无论如何也将不同,这样的构造函数是不需要的。
这可以使用模板元编程实现,具体而言是 SFINAE。我不会告诉你怎么做,也不会解释它是如何工作的。但这是重要的实现细节,因为一个 NamedType 的用户可以使用上面的表达式而不用移动构造函数。
代码是这样的:
template<typename T_ = T>
explicit NamedType(T&& value,
typename std::enable_if<!std::is_reference<T_>{},
std::nullptr_t>::type = nullptr)
: value_(std::move(value)) {}
这种构造的核心部分是 std::enable_if 旨在仅当特定条件为真时“启用”某些代码(在本例中为构造函数),前提是该条件在编译类型时可验证。并且可以在编译时检查 T 是否为引用。如果此条件不成立,则 enable_if 其模板替换失败。正如 SFINAE 所说,替换失败不是错误。所以代码编译了,构造函数就消失了。
一件特别的事情是必须 有一个替身,这意味着必须有一个模板参数。而且从构造函数的角度来看, T 不是模板参数,因为实例化构造函数 T 是已知的。这就是为什么我们人为地创建了一个新的模板参数 T_ ,它实际上与T相同。
如果你对前两段没有完全理解,或者懒得去挖掘,也没关系。要记住的是,您可以只使用以下类并将其包装在引用周围:
template <typename T, typename Parameter>
class NamedType
{
public:
explicit NamedType(T const& value) : value_(value) {}
template<typename T_ = T>
explicit NamedType(T&& value,
typename std::enable_if<!std::is_reference<T_>{},
std::nullptr_t>::type = nullptr)
: value_(std::move(value)) {}
T& get() { return value_; }
T const& get() const {return value_; }
private:
T value_;
};
如果你想使用它,所有的代码都在 GitHub 仓库 中。更多的帖子即将到来,描述新的功能,这些功能对于添加到强类型中非常有用。
这个系列绝对没有结束。