c++namespacesc++-concepts

`requires` expressions and function namespaces


How can I define a C++ constraint that involves a namespace-specified function? Example:

template<typename T>
concept C = requires(T t) {
    { N::foo(t) } -> std::same_as<void>;
};

struct S {};

namespace N {
    void foo(S) {}
}

static_assert(C<S>);

The requirements are that S must be in the global namespace, whereas foo must be within N.

Conceptually (pun intended), S does fulfil concept C. But the code is not compiling because N:: does not make sense to the compiler when the concept is defined. And of course, fwd-declaring void foo(S) in the namespace N before the concept definition is not an option, because I can't know type S in advance. What am I missing here?

EDIT: note that concept C can belong to whatever namespace. I've tried to define it within N, but that doesn't help.


Solution

  • In C++, name lookup must succeed at the point of definition even inside a requires expression or when defining a concept. This means if you write:

    template<typename T>
    concept IsValid = requires(T t) {
        { N::foo(t) } -> std::same_as<void>;
    };
    

    then the name N must be visible before the concept is defined. Otherwise, the compiler will emit an error like “N is not declared.”

    This isn't a limitation specific to concepts, it's a fundamental rule of how C++ performs name lookup during template definition. Unlike SFINAE (Substitution Failure Is Not An Error), concepts and requires expressions perform immediate (eager) name lookup, not deferred instantiation-time lookup.

    Below are several valid approaches to work around this limitation, depending on the specific constraints of your use case, which are :

    Each solution illustrates a different technique to work within these constraints.

    1. Concepts + ADL (Argument-Dependent Lookup)

    #include <concepts>
    #include <utility>
    
    template<typename T>
    concept C = std::same_as<decltype(foo(std::declval<T>())), void>;
    
    struct S {};
    namespace N {
        void foo(S) {} // Must return void
    }
    
    using N::foo;
    static_assert(C<S>);
    

    Note: We use using N::foo; before the static_assert to bring foo into the same namespace as S. This enables ADL to find foo correctly since S is in the global namespace and foo is in N.

    2. Concepts + Traits + Explicit Qualification

    #include <type_traits>
    #include <concepts>
    
    template<typename T>
    struct foo_traits {};
    
    template<typename T>
    concept C = std::same_as<typename foo_traits<T>::result_type, void>;
    
    struct S {};
    namespace N {
        void foo(S) {}
    }
    
    template<>
    struct foo_traits<S> {
        using result_type = decltype(N::foo(S{}));
    };
    
    static_assert(C<S>);
    

    Note: The trait is specialized after N::foo is defined, and the concept only uses the trait. This avoids the name lookup issue at concept definition time.

    3. Traits + SFINAE + ADL

    #include <type_traits>
    #include <utility>
    
    template<typename, typename = void>
    struct foo_traits : std::false_type {};
    
    template<typename T>
    struct foo_traits<T, std::enable_if_t<std::is_same_v<decltype(foo(std::declval<T>())), void>>> : std::true_type {};
    
    template<typename T>
    inline constexpr bool C = foo_traits<T>::value;
    
    struct S {};
    namespace N {
        void foo(S) {}
    }
    
    using N::foo;
    static_assert(C<S>);
    

    Note: This version uses SFINAE to delay substitution, allowing name lookup to succeed only if foo(t) is valid. As in example 1, using N::foo; ensures foo is visible to unqualified lookup via ADL.

    Using a Deleted foo Declaration

    A common workaround is to declare foo as a deleted function before defining the concept, then rely on overload resolution to select a valid foo later. However, this technique can have pitfalls due to unexpected candidate selection and surprising name lookup behavior. See this related question for more details:
    Function selection finding unexpected candidate when using namespaces