在类模板实例化中为某些参数显式使用默认值
Posted
技术标签:
【中文标题】在类模板实例化中为某些参数显式使用默认值【英文标题】:Explicitly use defaults for some parameters in class template instantiation 【发布时间】:2015-04-17 08:39:40 【问题描述】:一个类模板可以有多个参数,它们都有默认值。
template<typename UnderlyingT0 = int, typename UnderlyingtT1 = long, typename StringT = std::string>
struct options;
仅使用默认参数来初始化模板很容易:
options<> my_default_options;
但是如果我想更改参数子集怎么办?
options<int, int, std::wstring> wstring_options;
int
是第一个参数的默认值,而第二个不是。有没有类似的东西
options<default, int, std::wstring> wstring_options;
在 C++ 中?
【问题讨论】:
template <typename T1, typename T2> using limited_options = options<int, T1, T2>;
可以满足您的要求吗?我认为此示例中不接受默认关键字(尽管它会有意义)。
【参考方案1】:
不,标准 C++ 中没有任何东西可以实现这一点。 @FlorisVelleman 在 cmets 中指出的一种选择是引入别名模板:
template <class UnderlyingT1, class StringT = std::string>
using options_defT0 = options<int, UnderlyingT1, StringT>;
这样做的缺点是必须在别名定义中显式复制 UnderlyingT0
的默认参数,但至少它只在一个地方复制。
许多 Boost 库都使用另一种选项。他们引入了一个特殊的标签use_default
并使 that 成为默认值。像这样的:
struct use_default ;
template<typename UnderlyingT0 = use_default, typename UnderlyingtT1 = use_default, typename StringT = use_default>
struct options
using RealUnderlyingT0 = typename std::conditional<
std::is_same<UnderlyingT0, use_default>::value,
int,
UnderlyingT0
>::type;
using RealUnderlyingT1 = typename std::conditional<
std::is_same<UnderlyingT1, use_default>::value,
long,
UnderlyingT1
>::type;
using RealStringT = typename std::conditional<
std::is_same<StringT, use_default>::value,
std::string,
StringT
>::type;
;
这里的缺点是 1. 您无法通过查看模板声明来判断默认参数,以及 2. options<>
和 options<int, long, std::string>
是不同的类型。
前者可以通过良好的文档来解决,后者可以通过明智地使用转换函数和基类来帮助。
【讨论】:
很高兴知道这一点。我现在正在写一个 boost 库,所以我应该使用这种方法。 另一方面,这不适用于非类型模板参数,而 Sebastian Redls 的想法可以。【参考方案2】:没有办法直接重用默认参数。您可以使用 Floris 的注释作为一种为常用用途提供简写的方式,但模板别名仍将重复默认值。
或者,可以设置选项结构以允许切换参数:
template <typename UnderlyingT0 = int,
typename UnderlyingT1 = long,
typename StringT = std::string>
struct options
template <typename NewT0>
using WithT0 = options<NewT0, UnderylingT1, StringT>;
template <typename NewT1>
using WithT1 = options<UnderylingT0, NewT1, StringT>;
template <typename NewStringT>
using WithStringT = options<UnderylingT0, UnderylingT1, NewStringT>;
;
然后把它当作
options<>::WithT1<int>::WithStringT<std::wstring>
【讨论】:
编译时流畅的界面,一切都是第一次:) +1【参考方案3】:如果您的所有模板参数都具有示例中的默认值,您可以创建一个辅助结构来为您提取它们。
template <class T, size_t N>
struct default_for_helper;
template <template <typename...> class T, size_t N, typename... Args>
struct default_for_helper<T<Args...>, N>
using type = std::tuple_element_t<N, std::tuple<Args...>>;
;
template <template <typename...> class T, size_t N>
using default_for = typename default_for_helper<T<>, N>::type;
然后像这样使用它:
options<default_for<options,0>, int, std::string> o;
【讨论】:
非常好的技巧,当然缺点是当您只想更改最后一个参数时它会变得混乱。但它本身就是一个非常有趣的概念。 +1【参考方案4】:我又遇到了这个问题,想出了一个更通用的 Sebastian Redl 解决方案。
//given an index to replace at, a type to replace with and a tuple to replace in
//return a tuple of the same type as given, with the type at ReplaceAt set to ReplaceWith
template <size_t ReplaceAt, typename ReplaceWith, size_t... Idxs, typename... Args>
auto replace_type (std::index_sequence<Idxs...>, std::tuple<Args...>)
-> std::tuple<std::conditional_t<ReplaceAt==Idxs, ReplaceWith, Args>...>;
//instantiates a template with the types held in a tuple
template <template <typename...> class T, typename Tuple>
struct type_from_tuple;
template <template <typename...> class T, typename... Ts>
struct type_from_tuple<T, std::tuple<Ts...>>
using type = T<Ts...>;
;
//replaces the type used in a template instantiation of In at index ReplateAt with the type ReplaceWith
template <size_t ReplaceAt, typename ReplaceWith, class In>
struct with_n;
template <size_t At, typename With, template <typename...> class In, typename... InArgs>
struct with_n<At, With, In<InArgs...>>
using tuple_type = decltype(replace_type<At,With>
(std::index_sequence_for<InArgs...>, std::tuple<InArgs...>));
using type = typename type_from_tuple<In,tuple_type>::type;
;
//convenience alias
template <size_t ReplaceAt, typename ReplaceWith, class In>
using with_n_t = typename with_n<ReplaceAt, ReplaceWith, In>::type;
优点:
灵活选择要更改的参数 不需要改变原来的类 支持有一些参数没有默认值的类options<int,long,int>
和 with_n_t<2,int,options<>>
是同一类型
一些使用示例:
with_n_t<1, int, options<>> a; //options<int, int, std::string>
with_n_t<2, int,
with_n_t<1, int, options<>>> b; //options<int, int, int>
您可以进一步将其概括为采用可变参数对的索引和类型,这样您就不需要嵌套with_n_t
。
【讨论】:
好主意,但除非我弄错了,否则 Sebastian Redls 解决方案已经提供了您的四个优势中的三个,除了您的允许原始类保持不变。但这确实意味着您只需要在您的代码中导入一个标头并在您想要的任何地方使用它,这很好。不过,我个人更喜欢options<>::WithT0<int>
的语法,因为你实际使用的类写在语句的开头,而不是结尾。这似乎使代码更清晰。
是的,我只是认为这是解决问题的另一种有趣方式。您可以通过创建一个名为DefaultsSettable
的mixin 或其他东西来获得该语法,然后让您的类从该类继承以继承功能。这样你就可以写options<>::with_n_t<0,int>::with_n_t<1,float>
。以上是关于在类模板实例化中为某些参数显式使用默认值的主要内容,如果未能解决你的问题,请参考以下文章
处理使用typescript在类实例化中反转@inject()参数