2

I want to constraint the template parameters of a policy class. That is, when I call Foo<policy>, I want the compiler to stop here if the policy class does not fulfill the requirements I want.

Complete non-working example

To simplify the problem, let's consider just the requirement that the policy class has to declare a static variable that itself fulfill another concept (here, the Acceleration concepts from the mp-units library.

#include <units/isq/si/si.h>

using units::isq::Acceleration;

// A policy
struct earth
{
    // requirement seems to be fulfilled
    static inline constexpr Acceleration auto gravity = standard_gravity<>;
};

// Let's define a concept because I will need soon to use a set of more than 1 requirements
template <typename T>
concept SphericBody = requires(T)
{
  { T::gravity } -> Acceleration;
};

// The host class that has a constraint of the template argument
template<SphericBody T>
class Foo
{
  // ...
}

int main()
{
  Foo<earth> // does not compile :'(
}

It fails with the following compiler message:

‘T::gravity’ does not satisfy return-type-requirement
     { T::gravity } -> units::isq::Acceleration;

In the current version of the mp-units library, the Acceleration concept declaration is the following:

#include <units/concepts.h>
#include <units/isq/dimensions/length.h>
#include <units/isq/dimensions/time.h>

namespace units::isq {

template<typename Child, Unit U, typename...>
struct dim_acceleration;

template<typename Child, Unit U, DimensionOfT<dim_length> L, DimensionOfT<dim_time> T>
struct dim_acceleration<Child, U, L, T> : derived_dimension<Child, U, exponent<L, 1>, exponent<T, -2>> {};

template<typename T>
concept Acceleration = QuantityOfT<T, dim_acceleration>;

}  // namespace units::isq

What am I doing wrong?

I am aware of this related question: C++ Concepts - Can I have a constraint requiring a function be present in a class? but it focuses on non-static member variables.

Minimal working example

As requested by @HolyBlackCat, I tried my best to come with a minimal working example. The member variable is now a simple integer. Simply adding the requires clause works:

template <typename T>
concept HasGravity = requires(T t)
{
  { t.gravity } -> std::same_as<int&>;
};

struct myearth
{
    int gravity;
};

// The host class that has a constraint of the template argument
template<HasGravity T>
class Foo
{};

// using policy_t = Foo<earth> // compiles

Minimal NON working example

In this case, the requirement is exported to a concept, and it does not compile anymore.

template <typename T>
concept IsAcceleration = std::same_as<int&>;
};

// Let's define a concept because I will need soon to use a set of more than 1 requirements
template <typename T>
concept HasGravity = requires(T t)
{
  { t.gravity } -> IsAcceleration;
};

// A policy
struct myearth
{
    int gravity;
};

// The host class that has a constraint of the template argument
template<HasGravity T>
class Foo
{};

// using policy_t = Foo<earth> // does not compile

Error:

note: constraints not satisfied
test.cpp:45:9:   required for the satisfaction of ‘HasGravity<T>’ [with T = myearth]
test.cpp:45:22:   in requirements with ‘T t’ [with T = myearth]
test.cpp:47:7: note: ‘t.gravity’ does not satisfy return-type-requirement
   47 |   { t.gravity } -> IsAcceleration;
WaterFox
  • 850
  • 6
  • 18
  • 1
    How is `Acceleration` defined? Make a [mcve] please. – HolyBlackCat Jun 13 '22 at 20:48
  • 1
    Could you replace in `static inline constexpr Acceleration auto gravity` `auto` with the actual type? – Sebastian Jun 13 '22 at 20:52
  • @HolyBlackCat I added the concept definition from the [mp-unit](https://github.com/mpusz/units) library header file. It is minimal in the sense that I took out everything but the dependency that gives me a problem. – WaterFox Jun 13 '22 at 20:58
  • @Sebastian I don't think I can do that. I think the types are rather complicated. For example, the way they declare it in the documentation is `constexpr Acceleration auto expected = 9.81 * (m / (s*s));` But the type will change according to the measure system. – WaterFox Jun 13 '22 at 20:58
  • What is `QuantityOfT`? We want something that we can actually compile and reproduce the error ourselves. That should be explained in the link. – HolyBlackCat Jun 13 '22 at 20:59
  • @HolyBlackCat I understand your frustration, but it's like having a problem with a part of the boost library, right? You still end up having an external dependency that is not the STL in the explanation of your problem? – WaterFox Jun 13 '22 at 21:01
  • 1
    Does the same error happen with simpler types and a static variable? – Sebastian Jun 13 '22 at 21:06
  • 1
    @ArnaudBecheler An isolated example would still be nice, but I see your point. I think I see what's going on, I'll post an answer. – HolyBlackCat Jun 13 '22 at 21:06
  • Thank you people! @Sebastian I added simpler examples with a integer as a non-static member variable :) – WaterFox Jun 13 '22 at 21:55

1 Answers1

8

I'm not familiar with this library, but my guess is that the Acceleration concept rejects references.

{ expr } -> concept requirements determine the type as if by decltype((expr)), which for your variable yields an lvalue reference.


decltype inspects the value category of the expression, and adds & to types of lvalues and && to types of xvalues (prvalue types are unchanged). Since expressions can't have reference types, this doesn't lose any information.

decltype has a special case for variables - for them it returns the type as written, discarding the value category. By adding a second pair of parentheses, you disable this feature, falling back to the behavior described above.

HolyBlackCat
  • 78,603
  • 9
  • 131
  • 207
  • Thank you! I played around with references and could put together a working minimal example and a minimal non-working example :) – WaterFox Jun 13 '22 at 21:53
  • 1
    @ArnaudBecheler That's easy to do after you know the answer. :P – HolyBlackCat Jun 13 '22 at 21:58
  • Ahahaha true, true ;) But tbh it does not entirely solve my problem, as I still struggle understand why the last example does not work :'( – WaterFox Jun 13 '22 at 22:01
  • 1
    @ArnaudBecheler Seems to work after I fixed the typos: https://gcc.godbolt.org/z/dTKx4d97M – HolyBlackCat Jun 13 '22 at 22:15
  • 1
    @ArnaudBecheler You're not using Clang 12, are you? It had a bug where it used `decltype(??)` instead of `decltype((??))` for `{} ->` requirements. – HolyBlackCat Jun 13 '22 at 22:17
  • Oh godbolt is so nice! Well, i'm using `g++ (Ubuntu 10.3.0-1ubuntu1~20.04) 10.3.0 ` enabled for C++20 but with extensions turned off (I don't know what these do - will check). Ok so it seems to be a compiler problem. – WaterFox Jun 13 '22 at 22:24