gpt4 book ai didi

c++ - (Im) 使用可变参数模板完美转发

转载 作者:IT老高 更新时间:2023-10-28 22:35:45 26 4
gpt4 key购买 nike

概要

给定一个带有可变参数模板构造函数的类型,它将参数转发给一个实现类,是否可以限制使用 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;
};

通用引用的这种限制很有用,否则实现类将收到类型为 foo 的非常量左值引用。 ,它不知道。
完整示例 at 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;
}

(还有 at LWS。)

编辑:我发现 R. Martinho Fernandez 在 2012 年发表了关于此问题变体的博客: http://flamingdangerzone.com/cxx11/2012/06/05/is_related.html

最佳答案

以下是编写适当约束的构造函数模板的不同方法,按照复杂性递增的顺序和相应的功能丰富性的递增顺序和陷阱数量的递减顺序。

This particular form of EnableIf将使用,但这是一个实现细节,不会改变这里概述的技术的本质。还假设有 AndNot别名来组合不同的元计算。例如。 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);
};

福利:在以下场景中避免构造函数参与重载决议:
foo f;
foo g = f; // typical copy constructor taking foo const& is not preferred!

缺点 : 参与所有其他类型的重载决议

构造表达式的约束

由于构造函数具有构造 foo_impl 的道德效果。来自 Args ,用这些确切的术语来表达约束似乎很自然:
    template<
typename... Args
, EnableIf<
std::is_constructible<foo_impl, Args...>
>...
>
foo(Args&&... args);

福利:这现在正式成为受约束的模板,因为它仅在满足某些语义条件时才参与重载解析。

缺点:以下是否有效?
// function declaration
void fun(foo f);
fun(42);

例如,如果 foo_implstd::vector<double> ,那么是的,代码有效。因为 std::vector<double> v(42);是构造这种类型的 vector 的有效方法,那么从 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);

第二次尝试标记构造函数 explicit .

福利:避免以上缺点!而且也不需要太多——只要你不要忘记 explicit .

缺点:foo_implstd::string ,那么以下可能不方便:
void fun(foo f);
// No:
// fun("hello");
fun(foo { "hello" });

这取决于是否 foo例如,是围绕 foo_impl 的薄包装.假设 foo_impl,我认为这是一个更烦人的缺点。是 std::pair<int, double*> .
foo make_foo()
{
// No:
// return { 42, nullptr };
return foo { 42, nullptr };
}

我不喜欢 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>>
> {};

用法:
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);
};

福利: foo_impl的 build 与改造现在是 foo的构造和转换的充要条件.也就是说, std::is_convertible<T, foo>::value == std::is_convertible<T, foo_impl>::valuestd::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以避免重叠。

我们当中比较恭敬的人也会确保将狭义的转换与其他类型的转换区分开来。

关于c++ - (Im) 使用可变参数模板完美转发,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/13296461/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com