mav*_*vam 14 c++ variadic-functions sfinae variadic-templates c++11
给定具有可变参数模板构造函数的类型将参数转发给实现类,是否可以限制使用SFINAE转发的类型?
首先,考虑带有通用引用的构造函数的非变量情形.这里可以禁止通过SFINAE转发非常量左值引用来代替使用复制构造函数.
struct foo
{
foo() = default;
foo(foo const&)
{
std::cout << "copy" << std::endl;
}
template <
typename T,
typename Dummy = typename std::enable_if<
!std::is_same<
T,
typename std::add_lvalue_reference<foo>::type
>::value
>::type
>
foo(T&& x)
: impl(std::forward<T>(x))
{
std::cout << "uref" << std::endl;
}
foo_impl impl;
};
Run Code Online (Sandbox Code Playgroud)
这种通用引用的限制很有用,因为否则实现类将接收类型的非const左值引用foo
,它不知道.LWS的完整示例.
但是,如何使用可变参数模板?有可能吗?如果是这样,怎么样?天真的扩展不起作用:
template <
typename... Args,
typename Dummy = typename std::enable_if<
!std::is_same<
Args...,
typename std::add_lvalue_reference<foo>::type
>::value
>::type
>
foo(Args&&... args)
: impl(std::forward<Args>(args)...)
{
std::cout << "uref" << std::endl;
}
Run Code Online (Sandbox Code Playgroud)
(也在LWS.)
编辑:我发现R. Martinho Fernandez在2012年发表了关于这个问题的变体的博客:http://flamingdangerzone.com/cxx11/2012/06/05/is_related.html
Luc*_*ton 19
下面是编写适当约束的构造函数模板的不同方法,按照复杂度的增加顺序和相应的特征丰富度的递增顺序和陷阱数量的递减顺序.
将使用此特定形式的EnableIf,但这是一个实现细节,不会改变此处概述的技术的本质.还假设有And
和Not
别名组合不同的元计算.比如And<std::is_integral<T>, Not<is_const<T>>>
更方便std::integral_constant<bool, std::is_integral<T>::value && !is_const<T>::value>
.
我不推荐任何特定的策略,因为在构造函数模板方面,任何约束都比没有约束要好得多.如果可能的话,避免前两种技术有明显的缺点 - 其余的是对同一主题的阐述.
template<typename T>
using Unqualified = typename std::remove_cv<
typename std::remove_reference<T>::type
>::type;
struct foo {
template<
typename... Args
, EnableIf<
Not<std::is_same<foo, Unqualified<Args>>...>
>...
>
foo(Args&&... args);
};
Run Code Online (Sandbox Code Playgroud)
好处:避免构造函数在以下场景中参与重载解析:
foo f;
foo g = f; // typical copy constructor taking foo const& is not preferred!
Run Code Online (Sandbox Code Playgroud)
缺点:参加每一个其他类型的重载解析
由于构造函数具有构造foo_impl
from 的道德效应Args
,因此表达对这些确切术语的约束似乎很自然:
template<
typename... Args
, EnableIf<
std::is_constructible<foo_impl, Args...>
>...
>
foo(Args&&... args);
Run Code Online (Sandbox Code Playgroud)
好处:现在这是一个受约束的模板,因为只有满足某些语义条件才会参与重载解析.
缺点:以下是否有效?
// function declaration
void fun(foo f);
fun(42);
Run Code Online (Sandbox Code Playgroud)
例如,如果foo_impl
是std::vector<double>
,那么代码是有效的.因为std::vector<double> v(42);
是一个有效的方式构建这样类型的矢量,则它是有效的转换从int
到foo
.换句话说,std::is_convertible<T, foo>::value == std::is_constructible<foo_impl, T>::value
抛开其他构造函数的问题foo
(注意参数的交换顺序 - 很不幸).
当然,以下是立即想到的:
template<
typename... Args
, EnableIf<
std::is_constructible<foo_impl, Args...>
>...
>
explicit foo(Args&&... args);
Run Code Online (Sandbox Code Playgroud)
第二次尝试标记构造函数explicit
.
好处:避免上述缺点!它也不需要太多 - 只要你不忘记它explicit
.
缺点:如果foo_impl
是std::string
,则以下可能不方便:
void fun(foo f);
// No:
// fun("hello");
fun(foo { "hello" });
Run Code Online (Sandbox Code Playgroud)
这取决于foo
例如是否意味着是一个薄的包装foo_impl
.这是我认为更令人讨厌的缺点,假设foo_impl
是std::pair<int, double*>
.
foo make_foo()
{
// No:
// return { 42, nullptr };
return foo { 42, nullptr };
}
Run Code Online (Sandbox Code Playgroud)
我觉得explicit
实际上并没有把我从任何东西中拯救出来:在大括号中有两个参数,所以它显然不是转换,并且类型foo
已经出现在签名中,所以当我觉得它是时,我想随意使用它多余的.std::tuple
遭遇这个问题(尽管像工厂那样std::make_tuple
可以缓解这种痛苦).
让我们分别表达构造和转换约束:
// New trait that describes e.g.
// []() -> T { return { std::declval<Args>()... }; }
template<typename T, typename... Args>
struct is_perfectly_convertible_from: std::is_constructible<T, Args...> {};
template<typename T, typename U>
struct is_perfectly_convertible_from: std::is_convertible<U, T> {};
// New constructible trait that will take care that as a constraint it
// doesn't overlap with the trait above for the purposes of SFINAE
template<typename T, typename U>
struct is_perfectly_constructible
: And<
std::is_constructible<T, U>
, Not<std::is_convertible<U, T>>
> {};
Run Code Online (Sandbox Code Playgroud)
用法:
struct foo {
// General constructor
template<
typename... Args
, EnableIf< is_perfectly_convertible_from<foo_impl, Args...> >...
>
foo(Args&&... args);
// Special unary, non-convertible case
template<
typename Arg
, EnableIf< is_perfectly_constructible<foo_impl, Arg> >...
>
explicit foo(Arg&& arg);
};
Run Code Online (Sandbox Code Playgroud)
好处:建设和转换foo_impl
现在是建设和转换的必要和充分条件foo
.也就是说,std::is_convertible<T, foo>::value == std::is_convertible<T, foo_impl>::value
和std::is_constructible<foo, Ts...>::value == std::is_constructible<foo_impl, T>::value
两者保持(几乎).
退税? foo f { 0, 1, 2, 3, 4 };
如果foo_impl
是例如std::vector<int>
,则不起作用,因为约束是在风格的构造方面std::vector<int> v(0, 1, 2, 3, 4);
.可以添加一个std::initializer_list<T>
受限制的进一步过载std::is_convertible<std::initializer_list<T>, foo_impl>
(作为练习留给读者),甚至是过载std::initializer_list<T>, Ts&&...
(约束也留给读者练习 - 但要记住'转换'来自多个论证不是建构!).请注意,我们不需要修改is_perfectly_convertible_from
以避免重叠.
我们中间更加痴迷的人也会确保将狭隘的转换与其他类型的转换区分开来.
归档时间: |
|
查看次数: |
2188 次 |
最近记录: |