Understanding the Issue: Why Concept Checks Can Fail Silently

When working with C++20 concepts, checking for the existence of a static member function with a specific name and signature is a common requirement. However, developers often encounter a subtle gotcha where their concept checks evaluate to true regardless of whether the class actually contains the static function.

The Cause of the Always-True Behavior

Consider the following snippet from the original question:

export template<typename T, typename... Args>
concept require_static_function_name = requires(Args... args) {
    { T::something(args...) };
};

bool has_static = requires { require_static_function_name<MyClass>; }; // Always returns true!

The primary bug isn't inside the concept definition itself, but in how it is tested. The expression requires { require_static_function_name<MyClass>; } uses an anonymous requires expression that tests whether require_static_function_name<MyClass> is a syntactically valid expression.

Because require_static_function_name<MyClass> is a valid boolean concept-id (which evaluates to false at compile time), the syntax check itself succeeds! Thus, the nested requires block evaluates to true because evaluating the concept is a valid operation, even though the concept's result is false.

How to Properly Evaluate a Concept

To check the actual boolean result of a concept, evaluate it directly as a boolean value without wrapping it in an unconstrained requires block:

// Correct way to evaluate a concept value:
constexpr bool has_static = require_static_function_name<MyClass>;

If you want to use a nested requires expression inside another constraint, use the requires requires syntax (a nested requirement clause):

// Inside another requires block or template constraint:
requires require_static_function_name<MyClass>

Refining the Static Function Concept Definition

To make your concept robust, flexible, and capable of handling perfect forwarding, rewrite the concept as follows:

#include <utility>

template<typename T, typename... Args>
concept HasStaticSomething = requires(Args&&... args) {
    { T::something(std::forward<Args>(args)...) };
};

Complete Working Example

#include <iostream>
#include <utility>

template<typename T, typename... Args>
concept HasStaticSomething = requires(Args&&... args) {
    { T::something(std::forward<Args>(args)...) };
};

struct ValidClass {
    static void something(int x) {
        std::cout << "Called with: " << x << '\n';
    }
};

struct InvalidClass {
    // No 'something' function defined
};

int main() {
    static_assert(HasStaticSomething<ValidClass, int>, "ValidClass should satisfy the concept");
    static_assert(!HasStaticSomething<InvalidClass, int>, "InvalidClass should NOT satisfy the concept");

    std::cout << "Concept assertions passed successfully!\n";
    return 0;
}

Summary & Best Practices

  • Avoid syntax-checking concepts: Do not wrap concept-ids inside a plain requires { ... } block; evaluate the concept directly.
  • Use Perfect Forwarding: Use rvalue references Args&&... and std::forward inside concept expressions to properly match argument types.
  • Use static_assert for Verification: Verify your concepts using static_assert during compile-time testing rather than runtime variables.