Search code examples
c++namespacesc++20variadic-templatesc++-concepts

Concept that ONLY allows non-type template parameters that are members of certain namespace


I have a namespace Bar and a namespace Bar::Inner (declared within Bar).

// Bar
namespace Bar {
    size_t num = 32;
    
    // Bar::Inner
    namespace Inner {
        size_t num = 4;
    };
};

Then, I have a variadic function template foo. This variadic template accepts non-type parameters of common type T.
However, I want the non-type parameters to HAVE TO be members of namespace Bar, but NOT members of namespace Bar::Inner.

template<typename T, T ...Sz>
    requires MyConcept<T, Sz...>
auto foo()
{
    return (Sz + ...);
}

And I'd like to accomplish that using concepts, if possible.

template<typename T, T ...Sz>
concept MyConcept = (/*Whatever goes here*/);

So, to sum it up, I basically need a concept that can allow the non-type template parameters only if they are members of namespace Bar, but NOT of namespace Bar::Inner.


Solution

  • It's entirely possible to filter variables by namespace if you allow yourself to use some extensions.

    You have to also know that you won't accept values anymore. This may be a big downside.

    First, let's declare fixed_string, this is gonna be useful for compile time string manipulation:

    template<std::size_t n>
    struct fixed_string {
        constexpr fixed_string() = default;
        constexpr fixed_string(const char(&str)[n + 1]) noexcept {
            auto i = std::size_t{0};
            for (char const c : str) {
                _data[i++] = c;
            }
        }
        
        friend constexpr auto operator<=>(fixed_string const&, fixed_string const&) = default;
        
        [[nodiscard]]
        static constexpr auto size() noexcept -> std::size_t {
            return n;
        }
        
        constexpr auto data() const& noexcept {
            return _data;
        }
        
        constexpr auto data() & noexcept {
            return _data;
        }
        
        constexpr auto begin() const& noexcept -> char const* {
            return _data;
        }
        
        constexpr auto end() const& noexcept -> char const* {
            return _data + n;
        }
        
        constexpr auto begin() & noexcept -> char* {
            return _data;
        }
        
        constexpr auto end() & noexcept -> char* {
            return _data + n;
        }
        
        constexpr auto operator[](std::size_t index) noexcept {
            return _data[index];
        }
    
        constexpr auto starts_with(std::string_view str) {
            return std::string_view{_data, n}.starts_with(str);
        }
        
        char _data[n + 1];
    };
    
    template<std::size_t n>
    fixed_string(char const(&)[n]) -> fixed_string<n - 1>;
    

    Now that we have the basic, let's create a function that returns the qualified name of an object using a compiler extension. It's technically doable pretty portably if you use compiler specific extension and you dynamically filter the string so it returns only the name of the object.

    template<auto& v>
    constexpr auto name_of() -> std::string_view {
        constexpr auto size = std::string_view{__PRETTY_FUNCTION__}.size();
        constexpr auto drop_begin = "constexpr std::string_view name_of() [with auto& v = "sv.size();
        constexpr auto drop_end = "; std::string_view = std::basic_string_view<char>]"sv.size();
        return std::string_view{__PRETTY_FUNCTION__}.substr(drop_begin, size - drop_end - drop_begin);
    }
    

    Then, we can create a concept to only yield true of the qualified name starts with a particular string:

    template<auto& v, fixed_string namesp>
    concept in_namespace = name_of<v>().starts_with(std::string_view{namesp.data(), namesp.size()});
    

    Now, you can write a function that only accepts objects from a particular namespace:

    template<auto& v> requires in_namespace<v, "bar">
    void im_clever();
    

    To filter out the inner namespace you can do this:

    template<auto& v>
    concept in_bar_not_in_inner = in_namespace<v, "bar"> and not in_namespace<v, "bar::inner">;
    

    See my code running in compiler explorer