Explicitly use defaults for some parameters in class template instantiation

后端 未结 4 1763
囚心锁ツ
囚心锁ツ 2021-02-18 14:42

A class template can have multiple parameters that all have defaults.

template

        
相关标签:
4条回答
  • 2021-02-18 14:56

    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.

    0 讨论(0)
  • 2021-02-18 14:58

    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.

    0 讨论(0)
  • 2021-02-18 15:09

    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;
    
    0 讨论(0)
  • 2021-02-18 15:11

    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>
    
    0 讨论(0)
提交回复
热议问题