问题
A class template can have multiple parameters that all have defaults.
template<typename UnderlyingT0 = int, typename UnderlyingtT1 = long, typename StringT = std::string>
struct options;
Instatiating the template with just default parameters is easy:
options<> my_default_options;
But what if I want to change a subset of parameters?
options<int, int, std::wstring> wstring_options;
It is not obvious that int
is a default for the first parameter while for the second it isn't. Is there something like
options<default, int, std::wstring> wstring_options;
in C++?
回答1:
No, there is nothing in standard C++ which would enable this. One option, noted by @FlorisVelleman in the comments, is to introduce an alias template:
template <class UnderlyingT1, class StringT = std::string>
using options_defT0 = options<int, UnderlyingT1, StringT>;
This has the drawback of having to explicitly duplicate the default argument of UnderlyingT0
in the alias definition, but as least it' duplicated in one place only.
An alternative option is used by many Boost libraries. They introduce a special tag use_default
and make that the default value. Something like this:
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;
};
Here, the downsides are that 1. you cannot tell the default arguments by looking at the template declaration, and 2. options<>
and options<int, long, std::string>
are different types.
The former can be fixed by good documentation, and the latter can probably be helped by judicious use of conversion functions and base classes.
回答2:
There isn't a way to reuse the default parameters directly. You can use Floris's comment as a way to provide shorthands for common uses, but the template alias will still repeat the defaults.
Alternatively, the options struct could be set up to allow switching out parameters:
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>;
};
And then use it as
options<>::WithT1<int>::WithStringT<std::wstring>
回答3:
If all your template arguments have defaults like in your example, you could create a helper struct to extract them for you.
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;
Then use it like this:
options<default_for<options,0>, int, std::string> o;
回答4:
I came across this issue again and came up with a more general version of Sebastian Redl's solution.
//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;
Advantages:
- Flexible selection of parameters to change
- Doesn't require changing of the original class
- Supports classes which have some parameters without defaults
options<int,long,int>
andwith_n_t<2,int,options<>>
are the same type
Some usage examples:
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>
You could further generalise this to take variadic pairs of indices and types so that you don't need to nest with_n_t
.
来源:https://stackoverflow.com/questions/29694299/explicitly-use-defaults-for-some-parameters-in-class-template-instantiation