Explicitly use defaults for some parameters in class template instantiation

半世苍凉 提交于 2021-02-18 08:56:30

问题


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> and with_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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!