Envolvendo constantes não tipo do molde para evitar a mistura de parâmetros do mesmo tipo

votos
1

Eu tenho um método template tomando argumentos de modelo não de tipo. Ele tem a seguinte forma:

template <long long connectionTimeout, long long sendTimeout, bool autoAck>
void create() { ... }

É uma função de utilidade em outro cabeçalho, eo que me irrita no código do chamador é que as constantes não são digitados.

Ou seja, em vez de este modo de chamada:

create<1, 2, true>();

Eu prefiro ter o seguinte:

create<
    connection_timeout {1},
    send_timeout {2},
    auto_ack {true}
>();

Com a creategarantia função que um send_timeoutnão pode ser passado em vez de um connection_timeout.

Comecei a escrever uma prova de conceito, no entanto, com algumas lacunas. Eu gostaria de fazê-lo funcionar com C ++ 14/11. No entanto, eu tive que usar C ++ 17 construções (código cf) para fazer as coisas funcionarem até agora. Dito isto, eu não me importo soluções C ++ 17 para ter uma idéia se isso pode ser feito.

O que está faltando na seguinte é a verificação de tempo de compilação que os tipos de correspondência. No entanto, a sintaxe no principal é o que eu desejo ter.

#include <iostream>
#include <string>

template <typename T, T userSpecifiedValue>
struct compile_time_constant_wrapper
{
   using type = T;
   static const T defaultValue = userSpecifiedValue;

   constexpr operator T() const
   {
       return value;
   }

   T value = defaultValue;
};

using connection_timeout = compile_time_constant_wrapper<long long, 5000>;
using send_timeout = compile_time_constant_wrapper<long long, 10>;
using auto_ack = compile_time_constant_wrapper<bool, false>;

struct ComplicatedToBuild
{
    long long connectionTimeout;
    long long sendTimeout;
    bool autoAck;
};

template <typename T, 
          long long connectionTimeout = connection_timeout {} /*-std=c++17*/,
          long long sendTimeout = send_timeout {} /*-std=c++17*/,
          bool autoAck = auto_ack {} /*-std=c++17*/>
struct create
{
    operator T() const
    {
        return T{connectionTimeout, sendTimeout, autoAck};
    }
};

std::ostream& operator<<(std::ostream& out, const ComplicatedToBuild& complicated)
{
    out << connection timeout =  << complicated.connectionTimeout << , 
        << send timeout =  << complicated.sendTimeout << , 
        << auto ack =  << complicated.autoAck;
    return out;
}

int main()
{
    ComplicatedToBuild defaultValuesCase = create<ComplicatedToBuild>();
    std::cout << defaultValuesCase:  << defaultValuesCase << std::endl;

    ComplicatedToBuild customizedCase = create<
           ComplicatedToBuild,
           connection_timeout {2500},
           send_timeout {5},
           auto_ack {true}
    >();
    std::cout << customizedCase:  << customizedCase << std::endl;

    ComplicatedToBuild compilationErrorCase = create<
           ComplicatedToBuild,
           send_timeout {5},
           connection_timeout {2500},
           auto_ack {true}
    >();
}

No meu caso, a classe ComplicatedToBuildnão é uma estrutura simples. E os valores necessários para construí-lo são conhecidos em tempo de compilação. É por isso que eu pensei do uso de modelos não de tipo.

Publicado 27/11/2018 em 18:00
fonte usuário
Em outras línguas...                            


2 respostas

votos
4

#include <type_traits>    

enum class connection_timeout : long long {};
enum class send_timeout : long long {};
enum class auto_ack : bool {};

struct ComplicatedToBuild
{
    long long connectionTimeout;
    long long sendTimeout;
    bool autoAck;
};

template <typename T
        , connection_timeout connectionTimeout = connection_timeout{5000}
        , send_timeout sendTimeout = send_timeout{10}
        , auto_ack autoAck = auto_ack{false}>
T create()
{
    return {std::underlying_type_t<connection_timeout>(connectionTimeout)
          , std::underlying_type_t<send_timeout>(sendTimeout)
          , std::underlying_type_t<auto_ack>(autoAck)};
}

create<ComplicatedToBuild,
         connection_timeout{2500},
         send_timeout{5}, 
         auto_ack{true}>();

DEMONSTRA


Alternativamente, em vez de levantar um erro na argumento / tipo de parâmetro incompatibilidade, você pode permitir que os argumentos a serem especificados em uma ordem arbitrária:

#include <tuple>

template <typename T, auto Arg, auto... Args>
T create()
{
    auto t = std::make_tuple(Arg, Args...);
    return {
         std::underlying_type_t<connection_timeout>(std::get<connection_timeout>(t))
       , std::underlying_type_t<send_timeout>(std::get<send_timeout>(t))
       , std::underlying_type_t<auto_ack>(std::get<auto_ack>(t))
    };
}

template <typename T>
T create()
{
    return create<T, connection_timeout{5000}, send_timeout{10}, auto_ack{false}>();
}

create<ComplicatedToBuild,
         connection_timeout{2500},
         send_timeout{5},
         auto_ack{true}>();    

create<ComplicatedToBuild,
         auto_ack{true},
         send_timeout{5},
         connection_timeout{2500}>();

Demo 2

Respondeu 27/11/2018 em 18:50
fonte usuário

votos
3

Aqui está uma solução que atinge uma sintaxe ligeiramente diferente:

create<
    connection_timeout<1>,
    send_timeout<2>,
    auto_ack<true>
>();

Em primeiro lugar, precisamos de um is_instantiation_ofauxiliar:

template <typename T, template <auto...> class C>
struct is_instantiation_of_impl : std::false_type { };

template <auto... Ts, template <auto...> class C>
struct is_instantiation_of_impl<C<Ts...>, C>  : std::true_type { };

template <typename T, template <auto...> class C>
constexpr bool is_instantiation_of = is_instantiation_of_impl<T, C>::value;

Então, podemos definir nossos "typedefs fortes" como classes que herdam std::integral_constant:

template <long long X>
struct connection_timeout : std::integral_constant<long long, X> { };

template <long long X>
struct send_timeout : std::integral_constant<long long, X> { };

template <bool X>
struct auto_ack : std::integral_constant<bool, X> { };

Finalmente, nossa interface será parecido com este:

template <typename ConnectionTimeout,
          typename SendTimeout,
          typename AutoAck>
auto create()
    -> std::enable_if_t<
        is_instantiation_of<ConnectionTimeout, connection_timeout> 
     && is_instantiation_of<SendTimeout, send_timeout>
     && is_instantiation_of<AutoAck, auto_ack>
    >
{
}

viver exemplo em godbolt.org


Com uma mudança de interface mais dramático, o código pode ser muito mais simples:

template <long long A, long long B, bool C>
auto create(connection_timeout<A>, send_timeout<B>, auto_ack<C>)
{
}

int main()
{
    create(
        connection_timeout<1>{},
        send_timeout<2>{},
        auto_ack<true>{}
    );
}
Respondeu 27/11/2018 em 18:12
fonte usuário

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more