大括号在函数参数中初始化元组
Brace initialize tuple in function argument
问题的主要问题是我想编写一个函数,它接受两个可变长度的参数集。
我决定采用的抽象是模拟以下调用语法:
f({a,b,c},x,y);
如果 a
、b
和 c
都具有相同的类型,这可以与
一起使用
template <typename X, typename... A>
void f(std::initializer_list<X> xs, A&&... as) { . . . }
但是使用 std::tuple
而不是 std::initializer_list
的类似函数定义不允许我想要的语法。
template <typename... X, typename... A>
void f(const std::tuple<X...>& xs, A&&... as) { . . . }
我可以使用一些技巧来允许第一组中的类型是异构的吗?
补充:一般来说,我不知道X...
的大小。
附录 2:有谁知道为什么花括号初始化程序适用于 std::tuple t{a,b,c}
但不适用于模板参数上下文的技术原因?是因为在我的示例中 xs
没有被 X...
扩展吗?
不,目前无法使用您想要的语法。初始化列表只能推导为数组或std::initializer_list
,都是同类类型。
这是一个特殊的规则。初始值设定项列表没有类型,不能简单地推导出任何其他内容。如果函数参数不是数组或 std::initializer_list
,则为其提供初始值设定项列表的参数将变为 非推导上下文 并且模板参数推导将忽略它。
特别是 class 不能在函数参数中进行模板参数推导以确定花括号初始化器列表的类型,因为它可以在自 C++17 起的变量定义中完成。语言中没有添加这样的功能。
原则上你可以允许有限数量的不同类型,例如使用std::variant
的数组作为参数,但它只会在运行时解析为类型,不太可能是你想要的。
您需要将类型名称或函数调用添加到初始化列表大括号中,如
f(std::forward_as_tuple(a, b, c), x, y)
或者您可以使用标记类型来指示平面参数列表中第一个包的结尾:
f(a, b, c, tag, x, y)
然后可以使用一些模板工作来检测标签。
你想要的目前不方便,但是已经提供了另一种选择:
f(a, b, c)(x)(y)();
如果您利用 f
不 return 任何东西这一事实,您可以 return 一个 lambda 或其他接受更多参数的函数对象。一个空的参数列表可能意味着没有进一步的参数。
示例:
template <typename ...A>
auto f(A&& ...a)
{
if constexpr (sizeof...(a) > 1)
{
std::tuple(std::forward<A>(a)...);
}
else
{
// burp
}
return [](auto&& ...a)
{
if constexpr (sizeof...(a))
{
return f(std::forward<decltype(a)>(a)...);
}
else
{
std::cout << "done" << std::endl;
}
};
}
int main()
{
f(1, 2, 3)(4)(5)();
return 0;
}
https://wandbox.org/permlink/eAB2S4aCxxiSlMAF
编辑:也试试这个替代方案:
template <typename ...A>
void f(std::any const(&)[3], A&&...)
{
}
int main()
{
f({1, 2u, '3'}, 4., 5.f);
return 0;
}
https://wandbox.org/permlink/0OhoJpQ1YpSGOV2c
您可以使用 std::variant
来确保类型安全或比 STL 提供的速度更快的东西。当然你也可以写自己的容器。
EDIT2:再一次:
template <typename ...A>
void f(std::initializer_list<std::any>, A&&...)
{
}
int main()
{
f({1, 2u, '3'}, 4., 5.f);
return 0;
}
当然不要用std::any
,而是你自己喜欢的类型。
https://wandbox.org/permlink/rqSVK6aj5zuSFRcf
EDIT3:还有另一种方法:
class U
{
void const* const v_;
using typeid_t = void(*)();
typeid_t const tid_;
public:
template <typename T>
static typeid_t type_id() noexcept
{
return typeid_t(U::type_id<T>);
}
template <typename A>
U(A&& a) noexcept: v_(&a), tid_(type_id<std::remove_cvref_t<A>>()) {}
template <typename A>
A const& get() const noexcept { return *static_cast<A const*>(v_); }
auto type_id() const noexcept { return tid_; }
};
template <typename... A>
void f(U const (&a)[3], A&&...)
{
std::cout << a[0].get<double>() << std::endl;
}
int main()
{
f({1.5, 2, 3.f}, 'a', 1);
return 0;
}
您可以将 typeid
检查设为可选,并仅在调试版本中编译它。
问题的主要问题是我想编写一个函数,它接受两个可变长度的参数集。
我决定采用的抽象是模拟以下调用语法:
f({a,b,c},x,y);
如果 a
、b
和 c
都具有相同的类型,这可以与
template <typename X, typename... A>
void f(std::initializer_list<X> xs, A&&... as) { . . . }
但是使用 std::tuple
而不是 std::initializer_list
的类似函数定义不允许我想要的语法。
template <typename... X, typename... A>
void f(const std::tuple<X...>& xs, A&&... as) { . . . }
我可以使用一些技巧来允许第一组中的类型是异构的吗?
补充:一般来说,我不知道X...
的大小。
附录 2:有谁知道为什么花括号初始化程序适用于 std::tuple t{a,b,c}
但不适用于模板参数上下文的技术原因?是因为在我的示例中 xs
没有被 X...
扩展吗?
不,目前无法使用您想要的语法。初始化列表只能推导为数组或std::initializer_list
,都是同类类型。
这是一个特殊的规则。初始值设定项列表没有类型,不能简单地推导出任何其他内容。如果函数参数不是数组或 std::initializer_list
,则为其提供初始值设定项列表的参数将变为 非推导上下文 并且模板参数推导将忽略它。
特别是 class 不能在函数参数中进行模板参数推导以确定花括号初始化器列表的类型,因为它可以在自 C++17 起的变量定义中完成。语言中没有添加这样的功能。
原则上你可以允许有限数量的不同类型,例如使用std::variant
的数组作为参数,但它只会在运行时解析为类型,不太可能是你想要的。
您需要将类型名称或函数调用添加到初始化列表大括号中,如
f(std::forward_as_tuple(a, b, c), x, y)
或者您可以使用标记类型来指示平面参数列表中第一个包的结尾:
f(a, b, c, tag, x, y)
然后可以使用一些模板工作来检测标签。
你想要的目前不方便,但是已经提供了另一种选择:
f(a, b, c)(x)(y)();
如果您利用 f
不 return 任何东西这一事实,您可以 return 一个 lambda 或其他接受更多参数的函数对象。一个空的参数列表可能意味着没有进一步的参数。
示例:
template <typename ...A>
auto f(A&& ...a)
{
if constexpr (sizeof...(a) > 1)
{
std::tuple(std::forward<A>(a)...);
}
else
{
// burp
}
return [](auto&& ...a)
{
if constexpr (sizeof...(a))
{
return f(std::forward<decltype(a)>(a)...);
}
else
{
std::cout << "done" << std::endl;
}
};
}
int main()
{
f(1, 2, 3)(4)(5)();
return 0;
}
https://wandbox.org/permlink/eAB2S4aCxxiSlMAF
编辑:也试试这个替代方案:
template <typename ...A>
void f(std::any const(&)[3], A&&...)
{
}
int main()
{
f({1, 2u, '3'}, 4., 5.f);
return 0;
}
https://wandbox.org/permlink/0OhoJpQ1YpSGOV2c
您可以使用 std::variant
来确保类型安全或比 STL 提供的速度更快的东西。当然你也可以写自己的容器。
EDIT2:再一次:
template <typename ...A>
void f(std::initializer_list<std::any>, A&&...)
{
}
int main()
{
f({1, 2u, '3'}, 4., 5.f);
return 0;
}
当然不要用std::any
,而是你自己喜欢的类型。
https://wandbox.org/permlink/rqSVK6aj5zuSFRcf
EDIT3:还有另一种方法:
class U
{
void const* const v_;
using typeid_t = void(*)();
typeid_t const tid_;
public:
template <typename T>
static typeid_t type_id() noexcept
{
return typeid_t(U::type_id<T>);
}
template <typename A>
U(A&& a) noexcept: v_(&a), tid_(type_id<std::remove_cvref_t<A>>()) {}
template <typename A>
A const& get() const noexcept { return *static_cast<A const*>(v_); }
auto type_id() const noexcept { return tid_; }
};
template <typename... A>
void f(U const (&a)[3], A&&...)
{
std::cout << a[0].get<double>() << std::endl;
}
int main()
{
f({1.5, 2, 3.f}, 'a', 1);
return 0;
}
您可以将 typeid
检查设为可选,并仅在调试版本中编译它。