Search code examples
c++c++11constructordecltypetype-deduction

Is there a succinct way to derive a member's type during construction?


I have:

  • A template routine unarchive that takes a dictionary and a key and based on the template type (T) passed can be specialized to produce a T
  • A constructor for a struct that leverages unarchive to construct its members

An example might be the following:

template <typename T>
T unarchive(const dictionary_t&, key_type key);

struct foo
{
    foo(const dictionary& archive) :
        value_m(unarchive<decltype(value_m)>(archive, value_key))
    { }

    some_value_type value_m;
};

The advantage of using unarchive<decltype(value_m)> here is that I can change the type of value_m without having to update this line of code - the type always follows the member variable's type.

The problem I have is more aesthetic: it is very verbose. Currently I have a macro:

#define UNARCHIVE_FOR(var) unarchive<decltype(var)>

And the foo's constructor changes as follows:

foo(const dictionary& archive) :
    value_m(UNARCHIVE_FOR(value_m)(archive, value_key))
{ }

Now I have a result that is more terse but far uglier. Can the same result be achieved without a macro? What I would like would be something akin to:

foo(const dictionary& archive) :
    value_m(unarchive<value_m>(archive, value_key))
{ }

How can this be done?


Solution

  • The advantage of using unarchive here is that I can change the type of value_m without having to update this line of code - the type always follows the member variable's type.

    One alternative is to create an alias for the type of value_m and eliminate the decltype(value_m) from the constructor initializer list:

    struct foo
    {
        using value_type = int;
    
        foo(const dictionary_t& archive, const key_type value_key) :
            value_m(unarchive<value_type>(archive, value_key))
        { }
    
        value_type value_m;
    };
    

    the unarchive<value_type> still follows the type of value_m. A static_assert could be added to ensure the type of value_m is the same as value_type if there is concern of a change to the type of value_m by not changing value_type:

    static_assert(std::is_same<decltype(value_m), value_type>::value,
                  "'value_m' type differs from 'value_type'");
    

    or set the alias based on the type of value_m:

    int value_m;
    using value_type = decltype(value_m);
    

    If you still consider the constructor initialization list verbose provide a static wrapper function that invokes the unarchive() function:

    struct foo
    {
        using value_type = int;
    
        foo(const dictionary_t& archive, const key_type value_key) :
            value_m(unarchive_(archive, value_key))
        { }
    
        static value_type unarchive_(const dictionary_t& d, key_type k)
        {
            return unarchive<value_type>(d, k);
        }
    
        value_type value_m;
    };
    

    Having said all that:

    value_m(unarchive<decltype(value_m)>(archive, value_key))
    

    is not that verbose and precisely states the intention.