3

I want to write a concept that describes function objects that are not only invocable but, like std::function, have an "empty" state that is testable via contextual conversion to bool.

The boolean-testable exposition-only concept sounds like it might be appropriate but, since it is written in terms of convertible_to, requires that its argument be implicitly convertible to bool:

template<class T> concept BooleanTestable = std::convertible_to<T, bool>;
static_assert(BooleanTestable<void(*)()>); // ok
static_assert(BooleanTestable<std::function<void()>>); // fails :(

Is there something in the Standard <concepts> library that can help, or do I need to write a requires-expression myself, and if so, which form should I use? The definition of contextually converted to bool is expressed in the form of a declaration-statement (i.e., bool t(E);), but requires-expressions can only check expressions:

template<class T> concept ContextuallyConvertibleToBool = requires (T x) {
    bool(x); // constructor cast?
    static_cast<bool>(x); // or static_cast?
    x ? void() : void(); // or something a little stranger?
};

Considering subsumption, readability and maintenance, I'd prefer to use a form that has precedent.

ecatmur
  • 152,476
  • 27
  • 293
  • 366
  • Are you looking for [`is_explicitly_convertible`](https://stackoverflow.com/questions/16893992/check-if-type-can-be-explicitly-converted)? – Jarod42 Mar 02 '21 at 11:37
  • 1
    @Jarod42 Ah yes, `is_constructible_v` should work, or maybe `constructible_from` - of course, the arguments are in the wrong order so I still need to write my own concept. If there's nothing better I'll go with that. – ecatmur Mar 02 '21 at 12:49

1 Answers1

4

The wording contextually convertible is applicable to expressions, not types.

Certain language constructs require that an expression be converted to a Boolean value. An expression E appearing in such a context is said to be contextually converted to bool and is well-formed if and only if the declaration bool t(E); is well-formed, for some invented temporary variable t ([dcl.init]).

That's why the exact concept definition depends on your context and what exactly you want to constraint.

Below is an example of a type that passes constructible_from<bool, T> test, but fails to compile:

struct S {
    constexpr explicit operator bool() && noexcept {
        return false;
    }
};

void F() {
    S s;
    static_assert(std::constructible_from<bool, S>);
    if (s) {

    }
}

What we probably want above is std::constructible_from<bool, S&>. But this also affects a name of your concept, because you are now checking not the fact that your type is contextually convertible, but if your type can be used in a particular context. For example:

struct S1 {
    constexpr explicit operator bool() && noexcept {
        return false;
    }
};

struct S2 {
    constexpr explicit operator bool() noexcept {
        return false;
    }
};

template <typename T>
concept CanBeUsedWithF = std::constructible_from<bool, T&>;

template <CanBeUsedWithF T>
void F() {
    T s;
    if (s) {

    }
}

F<S1>();  // constraints not satisfied
F<S2>();
  • What you probably want is `static_assert(std::constructible_from);` to take all guesswork regarding types out of the equation. `std::constructible_from&>` is certainly not the correct test since `s` is not actually `const` (and though harmless in this case, `std::remove_reference_t` is superfluous). – ildjarn Jan 09 '23 at 23:15
  • It was done for illustration purposes, but I agree that in this particular context `s` is an non-const lvalue, I'll change the example to avoid confusion. – Nikolai Kozlov Jan 11 '23 at 14:30