5

So, I'd like to do some advanced type-level hackery for which I would really like to be able to write a concept that requires a type to have a constexpr int value associated with it, which I can use later in the same concept as the integer std::array template parameter.

It's possible to write

template<typename T>
concept bool HasCount = requires {
    typename T::count;
};

but this is not what I want; I would like T::count to be a static constexpr int. However, the code (not even including the needed constexpr)

template<typename T>
concept bool HasCount = requires {
    int T::count;
};

does not compile with "error: expected primary-expression before 'int'" on GCC 7.3.0.

Another failed attempt: it's possible to write this, which would require static int T::count():

template<typename T>
concept bool HasCount = requires {
    {T::count()} -> int;
};

but not this, which is what I want:

template<typename T>
concept bool HasCount = requires {
    {T::count()} -> constexpr int;
    {T::count() constexpr} -> int; // or this
    {constexpr T::count()} -> int; // or this (please forgive me for fuzzing GCC instead of reading the manual, unlike perl C++ is not an empirical science)
};

So, I'd like to know if it's in any way possible to require a concept expression to be constexpr-qualified, or if not if there's a reason why it can't be possible, or if it's just not included in the specification.

jcarpenter2
  • 5,312
  • 4
  • 22
  • 49
  • Don't know if it is possible to write it directly with concept, but traits can be done [detecting-constexpr-with-sfinae](https://stackoverflow.com/questions/15232758/detecting-constexpr-with-sfinae), and then use it in concept. – Jarod42 May 16 '18 at 07:54

1 Answers1

6

In theory, this is possible by requiring T::count to be a valid expression, and requiring that it's valid to use T::count in a context that requires a constant expression. For example:

#include <type_traits>
#include <utility>

template<int> using helper = void;

template<typename T>
concept bool HasCount = requires {
    // T::count must be a valid expression
    T::count;
    // T::count must have type int const
    requires std::is_same_v<int const, decltype(T::count)>;
    // T::count must be usable in a context that requires a constant expression
    typename ::helper<T::count>;
};

struct S1 {
    static constexpr int count = 42;
};
static_assert(HasCount<S1>);

struct S2 {
    int count = 42;
};
static_assert(!HasCount<S2>);

struct S3 {
    static constexpr double count = 3.14;
};
static_assert(!HasCount<S3>);

but in practice, the implementation of concepts in GCC rejects this program:

<source>:20:16: error: invalid use of non-static data member 'S2::count'
 static_assert(!HasCount<S2>);
                ^~~~~~~~~~~~
<source>:18:17: note: declared here
     int count = 42;
                 ^~
<source>:20:16: error: invalid use of non-static data member 'S2::count'
 static_assert(!HasCount<S2>);
                ^~~~~~~~~~~~
<source>:18:17: note: declared here
     int count = 42;
                 ^~

(which fact I believe to be a bug.)

Casey
  • 41,449
  • 7
  • 95
  • 125
  • 1
    It's not every day you stumble across [two compiler bugs](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=85808) writing a dozen lines of code for a Stackoverflow answer. – Casey May 16 '18 at 14:02
  • The compile error looks like it's because you [forgot `static` inside of `S2`](https://godbolt.org/g/3XiRZ5). One thing I really don't get is why the `::` preceding `helper` is needed. If I make `helper` (without `::`) the first line of `HasCount`, I don't get any compile error, but if I make it the second line (moving it after `T::count;`) I get the error `T::helper is not a type`, and if I make it the last line (simply removing the `::` from your code) I get `std::helper has not been declared`. Is namespace somehow carrying over from one line to the next?... Weird. – jcarpenter2 May 17 '18 at 01:39
  • Also, thanks but I feel inclined to mention that since writing that post I have arrived at a different and slightly lower-tech method of solving my actual problem: [recursive variadic templates which are specialized not to recurse at the last template argument](http://coliru.stacked-crooked.com/a/24e83cfd27bec09b) in order to easily define type-safe structs representing the result sets of various database queries. – jcarpenter2 May 17 '18 at 02:22
  • @jcarpenter2 I didn't forget `static` in `S2`, it's intentionally omitted to test that the concept correctly does not admit such a type. And yes, the name lookup bug depends on the order of requirements. – Casey May 17 '18 at 23:11