HolyBlackCat
HolyBlackCat

Reputation: 96749

Constexpr counter that works on GCC 8, and is not restricted to namespace scope

I'm trying to learn some arcane stateful template metaprogramming tricks.
(Here's why I want to learn it. Unfortunately this library doesn't work on GCC 8 nor on Clang.)

The first obvious thing I need is a constexpr counter:

/*something*/ constexpr int foo() /*something*/

int main()
{
    constexpr int a = foo();
    constexpr int b = foo();
    constexpr int c = foo();
    static_assert(a == 0 && b == 1 && c == 2);
}

Preferably it should be a tagged counter, so that I can have several counters at the same time:

/*something*/ constexpr int foo() /*something*/

struct TagA {};
struct TagB {};

int main()
{
    constexpr int a = foo<TagA>();
    constexpr int b = foo<TagA>();
    constexpr int c = foo<TagA>();

    constexpr int d = foo<TagB>();
    constexpr int e = foo<TagB>();
    constexpr int f = foo<TagB>();

    static_assert(a == 0 && b == 1 && c == 2);
    static_assert(d == 0 && e == 1 && f == 2);
}

I did some research, but alas, none of the counters I found worked with GCC 8.

I also found some implementations here: Does C++ support compile-time counters?, but most of them are limited to namespace scope, and others once again don't work with GCC 8.

What I did find was a simple proof-of-concept settable constexpr flag: http://b.atch.se/posts/non-constant-constant-expressions/

/*something*/ constexpr bool foo() /*something*/

constexpr bool a = foo();
constexpr bool b = foo();
constexpr bool c = foo();
static_assert (a == 0 && b == 1 && c == 1);

This one is not tagged, i.e. you can only have one per translation unit, which is not good.

I've managed to write my own tagged implementation based on it:

Usage:

int main()
{
    constexpr int c0_false = Meta::Flag<TagA>::ReadSet();
    constexpr int c0_true  = Meta::Flag<TagA>::ReadSet(); // Will continue to return true after this point.
    static_assert(c0_false == 0);
    static_assert(c0_true  == 1);

    constexpr int c1_false = Meta::Flag<TagB>::ReadSet();
    constexpr int c1_true  = Meta::Flag<TagB>::ReadSet(); // Will continue to return true after this point.
    static_assert(c1_false == 0);
    static_assert(c1_true  == 1);
}

Implementation:

namespace Meta
{
    template <typename T> class Flag
    {
        struct Dummy
        {
            constexpr Dummy() {}
            friend constexpr void adl_flag(Dummy);
        };

        template <bool> struct Writer
        {
            friend constexpr void adl_flag(Dummy) {}
        };

        template <class Dummy, int = (adl_flag(Dummy{}),0)>
        static constexpr bool Check(int)
        {
            return true;
        }

        template <class Dummy>
        static constexpr bool Check(short)
        {
            return false;
        }

      public:
        template <class Dummy = Dummy, bool Value = Check<Dummy>(0), int = sizeof(Writer<Value && 0>)>
        static constexpr int ReadSet()
        {
            return Value;
        }

        template <class Dummy = Dummy, bool Value = Check<Dummy>(0)>
        static constexpr int Read()
        {
            return Value;
        }
    };
}

(Try it live.)

Next, I tried to make an actual counter.

Desired usage:

constexpr int c0 = Meta::TaggedCounter<TagA>::Value();
constexpr int c1 = Meta::TaggedCounter<TagA>::Value();
constexpr int c2 = Meta::TaggedCounter<TagA>::Value();
static_assert(c0 == 0);
static_assert(c1 == 1);
static_assert(c2 == 2);

My naïve attempt: (For some reason it stops at 1.)

namespace Meta
{
    template <typename T> class TaggedCounter
    {
        template <int I> struct Tag {};

      public:
        template <int N = 0, bool B = Flag<Tag<N>>::ReadSet()> static constexpr int Value()
        {
            if constexpr (B)
                return 1 + Value<N+1>();
            else
                return 0;
        }
    };
}

(Try it live.)

How can I fix it?

Upvotes: 9

Views: 2544

Answers (2)

n0lavar
n0lavar

Reputation: 113

As I didn't find fully working version, I improved @anthony-williams solution by adding custom start and step value, and also added some compatibility with MVSC, clang and gcc (thanx to @segfault comment).
I hope it may me helpful.
code tests

namespace detail
{

template <typename T> class Flag
{
    struct Dummy
    {
        constexpr Dummy()
        {
        }
        friend constexpr void adl_flag(Dummy);
    };

    template <bool>
    struct Writer
    {
        friend constexpr void adl_flag(Dummy)
        {
        }
    };

    template <class Dummy, int = (adl_flag(Dummy{}), 0) >
    static constexpr bool Check(int)
    {
        return true;
    }

    template <class Dummy>
    static constexpr bool Check(short)
    {
        return false;
    }

public:

    template <class Dummy = Dummy, bool Value = Check<Dummy>(0)>
    static constexpr bool ReadSet()
    {
        Writer<Value && 0> tmp{};
        (void)tmp;
        return Value;
    }

    template <class Dummy = Dummy, bool Value = Check<Dummy>(0)>
    static constexpr int Read()
    {
        return Value;
    }
};

template <typename T, int I>
struct Tag
{

    constexpr int value() const noexcept
    {
        return I;
    }
};

template<typename T, int N, int Step, bool B>
struct Checker
{
    static constexpr int currentval() noexcept
    {
        return N;
    }
};

template<typename T, int N, int Step>
struct CheckerWrapper
{
    template<bool B = Flag<Tag<T, N>>{}.Read(), int M = Checker<T, N, Step, B>{}.currentval() >
    static constexpr int currentval()
    {
        return M;
    }
};

template<typename T, int N, int Step>
struct Checker<T, N, Step, true>
{
    template<int M = CheckerWrapper<T, N + Step, Step>{}.currentval() >
    static constexpr int currentval() noexcept
    {
        return M;
    }
};

template<typename T, int N, bool B = Flag<Tag<T, N>>{}.ReadSet() >
struct Next
{
    static constexpr int value() noexcept
    {
        return N;
    }
};

}

template <class Tag = void, int Start = 0, int Step = 1>
class constexpr_counter
{
public:
    template <int N = detail::CheckerWrapper<Tag, Start, Step>{}.currentval()>
    static constexpr int next()
    {
        return detail::Next<Tag, N>{}.value();
    }
};

examples:

using counter_A_0_1 = constexpr_counter<struct TagA, 0, 1>;
constexpr int a0 = counter_A_0_1::next();
constexpr int a1 = counter_A_0_1::next();
constexpr int a2 = counter_A_0_1::next();
static_assert(a0 == 0);
static_assert(a1 == 1);
static_assert(a2 == 2);

using counter_B_0_1 = constexpr_counter<struct TagB, 0, 1>;
constexpr int b0 = counter_B_0_1::next();
constexpr int b1 = counter_B_0_1::next();
constexpr int b2 = counter_B_0_1::next();
static_assert(b0 == 0);
static_assert(b1 == 1);
static_assert(b2 == 2);

using counter_C_2_1 = constexpr_counter<struct TagC, 2, 1>;
constexpr int c0 = counter_C_2_1::next();
constexpr int c1 = counter_C_2_1::next();
constexpr int c2 = counter_C_2_1::next();
static_assert(c0 == 2);
static_assert(c1 == 3);
static_assert(c2 == 4);

using counter_D_4_1 = constexpr_counter<struct TagD, 4, 1>;
constexpr int d0 = counter_D_4_1::next();
constexpr int d1 = counter_D_4_1::next();
constexpr int d2 = counter_D_4_1::next();
static_assert(d0 == 4);
static_assert(d1 == 5);
static_assert(d2 == 6);

using counter_E_5_3 = constexpr_counter<struct TagE, 5, 3>;
constexpr int e0 = counter_E_5_3::next();
constexpr int e1 = counter_E_5_3::next();
constexpr int e2 = counter_E_5_3::next();
static_assert(e0 == 5);
static_assert(e1 == 8);
static_assert(e2 == 11);

using counter_F_2_m3 = constexpr_counter<struct TagF, 2, -3>;
constexpr int f0 = counter_F_2_m3::next();
constexpr int f1 = counter_F_2_m3::next();
constexpr int f2 = counter_F_2_m3::next();
static_assert(f0 == 2);
static_assert(f1 == -1);
static_assert(f2 == -4);

Upvotes: 2

Anthony Williams
Anthony Williams

Reputation: 68651

The body of a constexpr function template must yield the same answer for all instatiations with the same template parameters and same arguments. You need to add a level of indirection, so the calculation can happen in the default argument of a template parameter dependent on the first.

See https://gcc.godbolt.org/z/GHfKKf

namespace Meta
{
    template <typename T,int I> struct Tag {};

    template<typename T,int N,bool B>
    struct Checker{
        static constexpr int currentval() noexcept{
            return N;
        }
    };

    template<typename T,int N>
    struct CheckerWrapper{
        template<bool B=Flag<Tag<T,N>>::Read(),int M=Checker<T,N,B>::currentval()>
        static constexpr int currentval(){
            return M;
        }
    };

    template<typename T,int N>
    struct Checker<T,N,true>{
        template<int M=CheckerWrapper<T,N+1>::currentval()>
        static constexpr int currentval() noexcept{
            return M;
        }
    };

    template<typename T,int N,bool B=Flag<Tag<T,N>>::ReadSet()>
    struct Next{
        static constexpr int value() noexcept{
            return N;
        }
    };

    template <typename T> class TaggedCounter
    {
      public:
        template <int N=CheckerWrapper<T,0>::currentval()> static constexpr int Value(){
            return Next<T,N>::value();
        }
    };
}

Upvotes: 9

Related Questions