Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

  • https://github.com/DaemonSnake/unconstexpr - Works with GCC 7, doesn't work with GCC 8 nor with Clang 6. (Try it online.)
  • http://b.atch.se/posts/constexpr-counter/ - Once again, works with GCC 7 and doesn't work with GCC 8 nor with Clang 6. (Try it online.)

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?

like image 201
HolyBlackCat Avatar asked Jul 30 '18 19:07

HolyBlackCat


2 Answers

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();
        }
    };
}
like image 184
Anthony Williams Avatar answered Nov 14 '22 02:11

Anthony Williams


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);
like image 2
n0lavar Avatar answered Nov 14 '22 01:11

n0lavar