Home > Blockchain >  How to initialize constexpr static class members per class instantiation basis?
How to initialize constexpr static class members per class instantiation basis?

Time:07-29

Basically, I want to allow the clients of the class Foo to define its static constexpr member variables using arbitrary values based on the template type argument they pass to it when instantiating Foo.

Here is an MRE:

#include <iostream>
#include <concepts>


template < std::unsigned_integral size_type,
           class Allocator = std::allocator<char> >
class Foo
{
public:
    static constexpr size_type constant1 { 20 };
    static constexpr size_type constant2 { 30 };

    void dummy_func( ) const
    {
        std::cout << constant1 << ' '
                  << constant2 << '\n';
    }
};

int main( )
{
    Foo<std::uint32_t> fooInstance1;
    fooInstance1.dummy_func( ); // prints: 20 30

    // I want these static members to be initialized
    // by the client but this dummy solution does not work
    // Foo<std::uint64_t>::constant1 { 120 };
    // Foo<std::uint64_t>::constant2 { 130 };

    Foo<std::uint64_t> fooInstance2;
    fooInstance2.dummy_func( ); // should print: 120 130
}

Note that the values 20 and 30 are for demonstration purposes and don't need to be inside the class since I want to force the client to decide on their own which values they want their version of Foo to have for its constant1 and constant2.

I have also taken a look at this similar question but couldn't make it work for the above specific case.

One of the possible approaches that come to my mind is to use variable templates instead. But I'm not sure how. Another one is an explicit instantiation of Foo. Or maybe partial instantiation?

Now I want to mention that the class should obviously be able to go in a header file and then be included in whichever source file that needs to instantiate it and use it.

Is there a simple method to achieve this?

CodePudding user response:

ITNOA

My assumption is Foo class has many static constexpr variables and writer of Foo class does not like to write long template signature

So I hope to below solution is simple and scalable enough for Foo class writer

template <std::unsigned_integral size_type, size_type... args>
class Foo
{
public:
    static constexpr size_type constant1{ std::get<0>(internal_values) };
    static constexpr size_type constant2{ std::get<1>(internal_values) };

    void dummy_func() const
    {
        std::cout << constant1 << ' ' << constant2 << std::endl;
    }

private:
    static constexpr std::tuple<size_type, size_type> internal_values{ std::tuple<decltype(args)...>(std::forward<size_type>(args)...) };
};

and for use of this class you can just write simple code like below

int main()
{
    auto fooInstance1 = Foo<std::uint32_t, 20, 30>();
    fooInstance1.dummy_func(); // prints: 20 30
    auto fooInstance2 = Foo<std::uint64_t, 200, 300>();
    fooInstance2.dummy_func(); // prints: 200 300
}

CodePudding user response:

It seems that constexpr value must be initialized when they are declared. Thus it look like you have to either do specialization (probably by adding template parameters as suggested in a comment and the derive from that class).

Having said that, if regular static constant works, you can also do :

#include <iostream>
#include <concepts>

template < std::unsigned_integral size_type,
    class Allocator = std::allocator<char> >
    class Foo
{
public:
    // (1) const is used here instead of constexpr
    static const size_type constant1;
    static const size_type constant2;

    void dummy_func() const
    {
        std::cout << constant1 << ' '
            << constant2 << '\n';
    }
};

// (2) Definitions cannot be inside a function.
// (3) Proper syntax must be used to define a constant out-of-class.
// (4) You must provide definition for both 32 and 64 bit since you use both.
// (5) You should have a single definition per type.
template <>
const std::uint32_t Foo<std::uint32_t>::constant1{ 120 };

template <>
const std::uint32_t Foo<std::uint32_t>::constant2{ 130 };

template <>
const std::uint64_t Foo<std::uint64_t>::constant1{ 220 };

template <>
const std::uint64_t Foo<std::uint64_t>::constant2{ 230 };


int main()
{
    Foo<std::uint32_t> fooInstance1;
    fooInstance1.dummy_func(); // prints: 120 130

    Foo<std::uint64_t> fooInstance2;
    fooInstance2.dummy_func(); // prints: 220 230
}

With that, you will get the following output :

120 130
220 230

Why are you specifying an allocator parameter? Do you intent to specify distinct constants for each allocator?

CodePudding user response:

Seems overly complex. If client shall set both constants, why you do not write a constructor with parameters?

#include <iostream>
template <typename size_type,
class Allocator = std::allocator<char> >
class Foo
{
public:
    Foo(size_type c1=20, size_type c2=30) :
        constant1(c1),
        constant2(c2) {}
    const size_type constant1;
    const size_type constant2;

    void dummy_func() const
    {
        std::cout << constant1 << ' '
            << constant2 << '\n';
    }
};


int main()
{
    Foo<std::uint32_t> fooInstance1;
    fooInstance1.dummy_func(); // prints: 20 30
    
    Foo<std::uint64_t> fooInstance2(120, 130);
    fooInstance2.dummy_func(); // should print: 120 130
}

I removed the <concepts> stuff, its C 20 and I am stuck with C 14. What I do not understand:

Are these constants specific to the type of size_type?

If yes, why then should a Client (User) of your Foo class set these values? You should better provide a non-template implementation of your Foo class for all needed types with the correct constants. (of course each implementation may inherit from your template class)

Are these constants independent of the type of size_type?

If yes, why then should they be static constexpr? What if two Clients want to instantiate your class with different constants?

You should always have a solid reason to even think about writing the word "static". Static is mostly evil (I know... its just my opinion).

  • Related