mardi 27 novembre 2018

Wrapping non type template constants to avoid mixing parameters of same type

I have a template method taking non type template arguments. It has the following form:

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

It is a utility function in another header, and what annoys me in the caller's code is that the constants are not typed.

Meaning, instead of this way of calling:

create<1, 2, true>();

I prefer to have the following:

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

With the create function guaranteeing that a send_timeout cannot be passed instead of a connection_timeout.

I started writing a proof of concept, however, with some gaps. I'd like to make it work with C++11/14. However, I had to use C++17 constructs to make things work until now. That being said, I don't mind C++17 solutions to get an idea if this can be done.

What is lacking in the following is the compile time check that the types match. However, the syntax in the main is what I desire to have.

#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 {}, long long sendTimeout = send_timeout {}, bool autoAck = auto_ack {}>
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}
    >();
}

In my case, the class ComplicatedToBuild is not a plain struct. And the values required to build it are known at compile time. This is why I thought of using non type templates.

Aucun commentaire:

Enregistrer un commentaire