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

2

9

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?

Heterosexual answered 30/7, 2018 at 19:59 Comment(7)
One thing: IIRC, this became a DR to be removed from the standard. Stateful metaprogramming is not intended to be allowed by the standard, so don't be surprised if the standard is changed to invalidate any stateful metaprogramming whenever it comes outPenneypenni
@Penneypenni Yeah, I've heard something like that... But do you have any links to the defect reports? I've heard about the commitette planning to somehow forbid it, but never seen actual DRs.Heterosexual
Here's the best I could find: https://mcmap.net/q/20975/-is-stateful-metaprogramming-ill-formed-yet . Looks like there isn't a DR yet, but the committee's consensus is that it should be ill-formed (they just haven't yet decided how to rule it as ill-formed)Penneypenni
I believe all template based stateful metaprogramming are officially ill-formed NDR as of C++17 due to [temp.res]/8.4Lepidote
@PasserBy Was this done as a DR or part of the original C++17 standard?Hsiuhsu
@JonHarper It's cwg 1850.Lepidote
@PasserBy Ugh, that's unfortunate. If you post it as an answer, and show how the stateful templates above violate those rules, I'll accept it.Heterosexual
U
9

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();
        }
    };
}
Ungrudging answered 2/10, 2019 at 11:7 Comment(6)
Hm... What's interesting, while the referenced code works like a charm with any GCC version available to me (and MSVC as well), it doesn't work at all with Clang. It looks like Clang is more lazy with template instantiation. The biggest issue is with ReadSet definition, it seems that using Worker class when deducing template parameters does not force corresponding adl_flag() instantiation.Schnapps
After some experiments I found the following helps with Clang >= 9: 1) Change all static method calls from e.g. CheckerWrapper<T,0>::currentval()> to CheckerWrapper<T,0>{}.currentval()>. 2) Remove last template parameter for ReadSet(). 3) Create a temporary Worker object inside ReadSet(). I.e. ReadSet() now looks like template <class Dummy = Dummy, bool Value = Check<Dummy>(0)> static constexpr bool ReadSet() { Writer<Value && 0> tmp{}; (void)tmp; return Value; } However this does not work with earlier ClangSchnapps
For the Clang <= 8 I was able to instantiate adl_flag() only by creating static constexpr member in Worker and initializing it by adl_flag() call. If I reference that member later, adl_flags() instantiates successfully. But this solution fails with Clang >=9. What's interesting, GCC and MSVC are more tolerant and work fine with any of described changes :)Schnapps
I am having a hard time understanding this code. Would the OP be gentle enough to add step-by-step explanation to it? Meanwhile, I've come up with an alternative solution that works on all versions of g++ and clang++, and also on msvc and icc, it can be found here: https://mcmap.net/q/20861/-c-compile-time-counters-revisited.Rung
I've played around with the OP's solution and discovered that it has a couple quirks. 1) an expression like int x = C::Value() + C::Value() + C::Value() + C::Value() gives 3 as a result with gcc and 0 with msvc, rather than the expected 6; however, if the counter is wrapped up into a template function, it works, but only if the value is extracted as part of the template parameter default value. 2) Filling up an array with the counter values only works if done through the wrapper function. See it all on godbolt: gcc.godbolt.org/z/w8ZkGXRung
I would really love if someone cared enough to explain what is really going there, and what the standard really mandates.Rung
C
2

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);
Catawba answered 25/8, 2020 at 12:21 Comment(2)
Looks promising! Can you copypaste the implementation into the answer itself as text? The rules require that, since the link might break one day.Heterosexual
Nice. Magical. @Heterosexual was right. The code link is dead and doesn't lead to the code anymore. Also, I ran into a problem. We can't just write std::cout << counter_A_0_1::next() << counter_A_0_1::next(). The output will be just 00. We have to assign constexpr variables and only then use the variables. Which was a problem for me inside macros that now needed to declare variables, which need to have unique names. Ended up wrapping the code in {} to create inner scope to hide the constexpr counter variables from one another when macros are used next to each other.Geniculate

© 2022 - 2024 — McMap. All rights reserved.