Is there a way to use a using-declaration inside a requires-expression
Asked Answered
A

3

25

I want to test whether a type can be passed to some function, but I'd like to use ADL on the function lookup and include a function from a certain namespace.

Consider this code:

#include <utility>
#include <vector>

template<class T>
concept Swappable = requires(T& a, T& b)
{
    swap(a,b);
};

static_assert(Swappable<std::vector<int>>); // #1
static_assert(Swappable<int>); // #2

#1 succeeds, it finds std::swap because std is an associated namespace of std::vector<int>. But #2 fails, a built-in type has no associated namespace.

How would I write something like:

template<class T>
concept Swappable = requires(T& a, T& b)
{
    using std::swap; // illegal
    swap(a,b);
};

AFAIK, you're not allowed to use a using-declaration inside a requires-expression.

(NOTE Although there is a perfectly fine standard C++ concept for this, std::swappable, this example uses swap for exposition only. I'm not particularly looking to test whether something is actually swappable, I'm just trying to find a way to implement such a concept where a customization function has a default implementation in a known namespace, but might have overloads in an associated namespace.)

EDIT As a workaround, I can implement the concept in a separate namespace where the names are pulled in. Not too happy about it but it works.

namespace detail
{
    using std::swap;

    template<class T>
    concept Swappable = requires(T& a, T& b)
    {
        swap(a,b);
    };
}

// and then either use it
using detail::Swappable;

// or redefine it
template<class T>
concept Swappable = detail::Swappable<T>;
Athalia answered 2/12, 2022 at 17:3 Comment(16)
Is this actually a helpful concept? If you constrain a template parameter on Swappable, you still have to using std::swap to be safeBipetalous
But in my particular case, I want the code to behave a certain way if a certain set of functions are defined for it, and in another way if it doesn't. So I want to have a concept to be able to test for it.Athalia
@oisyn: "I want the code to behave a certain way if a certain set of functions are defined for it, and in another way if it doesn't." Then you're going to have to explain what that behavior is, and why you need it to have that behavior. If the example you give is over-simplified, then we're going to give you overly-simplistic answers.Autrey
My question is simple. How do I use using declarations inside a requires expression. I'm asking from a language perspective, I don't care about a potential xy-problem.Athalia
@oisyn: "I'm asking from a language perspective, I don't care about a potential xy-problem." Concepts don't let you do this precisely because you shouldn't do this. That is, during the design of the feature, the idea probably came up, and they said, "we shouldn't be doing that to begin with; there are better ways to solve that problem." Whether you personally care about the XY issue, the language (and your users) definitely does care.Autrey
Just to clarify in case of std::vector Argument Dependent Lookup (ADL) kicks in. You should just use std::vetor(a, b);Upright
@oisyn: And note that users of this concept will not appreciate having to shove using declarations everywhere either. The language is not meant for you to be able to do this, and your interface should accept that.Autrey
Can you explain why you didn't consider to use std::vetor(a, b);?Upright
@NicolBolas Users won't use this concept. I just need it to implement behavior for some generic types. I'm writing a serialization library where users call a function for the members of the struct they want serialized. They only need to customize this function for their types, but I want to have standard implementations for container-like and tuple-like types. Rather than having to write an overload for each container type, I'd like to test whether it supports an iterable range using begin(c)/end(c), similar for get<N>(t) for tuples. Unfortunately, this not only applies to std.Athalia
@MarekR Not sure what you mean by "use std::vetor(a,b)" (I'm assuming you mean vector here instead of vetor, but that doesn't make it clearer).Athalia
Sidenote, there is std::swappablePeevish
@Athalia just thinking about one thing and typing other. It suppose to be std::swap(a, b); :).Upright
@MarekR You're not allowed to overload functions in std, so if you want to implement swap() for your own type, you'll need to do that outside of std. As a result, any generic code directly calling std::swap() bypasses your specialized implementation. That's why it's recommended to do the using std::swap; swap(a, b); idiom in generic code, because that way it finds the std implementation as well as any other associated namespace of a and b. Of course, std::ranges::swap() takes care of this boilerplate for you, as @NicolBolas explained, so that would be the preferred way.Athalia
why someone like to overload std::swap?Upright
Not std::swap but swap. That's why you want to call swap instead of std::swap.Athalia
EOT for me since apparently we are not on the same page.Upright
B
23

You can put it inside a lambda:

template<class T>
concept Swappable = []{
    using std::swap;
    return requires(T& a, T& b) { swap(a, b); };
}();
Baran answered 2/12, 2022 at 18:5 Comment(1)
Nice, cool trick! I experimented with lambda's to solve my issue, but the requires-expression doesn't check their bodies, any errors in there are just hard compiler errors. But I hadn't considered returning a requires expression from a lambda!Athalia
A
5

Avoid using old using-based idioms. Instead, use the customization point equivalents like ranges::swap.

That is, you should not require users to use using-based idioms in their code. Provide a customization point object that does what it needs to. The operator() overloads/templates can be constrained to create the effect of the using idiom without requiring the user to actually invoke using.

ranges::swap is a good example of how this gets done.

Autrey answered 2/12, 2022 at 17:14 Comment(8)
Some people need to deal with the mountains of existing code that uses using-base idioms though...Dignitary
Yes that's mainly my issue here as well. Also, I think the language customization points are better from a user-perspective, but I don't yet fully understand them, let alone that I can expect my team to maintain them. It's just terrible C++ code from a readibility standpoint.Athalia
@oisyn: "It's just terrible C++ code from a readibility standpoint." And throwing a bunch of using directives everywhere isn't? ranges::swap(...) is way more readable than using std::swap; swap(...). And even better, it can be used in places where you can't have using directives. Like... concepts ;)Autrey
For swap - probably yes, but to write your own std::ranges::swap-like object, you'll need a way to use using in a concept anyway.Telegony
@HolyBlackCat: Not really. The reason you need using std::swap is for types where the swap function is defined in the std namespace but cannot otherwise be accessed. But these are only for fundamental types; all user-defined types are supposed to rely on ADL for swapping. So ranges::swap::operator() simply uses concepts to check if the type is a fundamental type and does the swapping itself.Autrey
@NicolBolas Now this is an interesting point you raise here. I can work around it in a similar way for built-in types.Athalia
@NicolBolas Objects of class type can be swappable without finding swap via ADL, using the "default" behaviour of std::swap and move constructing/assigning, so checking for fundamental types isn't enough. Example: godbolt.org/z/j9MYGr43rBaran
@Artyer: I didn't say that was the entirety of the algorithm. Obviously, it also needs to check for member swap, and then to check if the types are moveable/constructable, etc. The point being that you can do all of the stuff that using std::swap does without actually doing that.Autrey
T
-2

Interesting, the below code works well again:

#include <concepts>

    template <typename T, typename U = T>
    concept Swappable = requires(T&& a, U&& b) {
        std::swap(a, b);
    };
    
    int main() {
        static_assert(Swappable<int>); 
        static_assert(Swappable<int, int>);
        return 0;
    }

I see an example from cppconference, but I could not get expected results. Cpp reference. See code here:

template <typename T, typename U = T>
concept Swappable = requires(T&& a, U&& b) {
    swap(std::forward<T>(a), std::forward<U>(b));
    swap(std::forward<U>(b), std::forward<T>(a));
};

int main() {
    static_assert(!Swappable<int>); // Wrong, int is definately swappable!
    static_assert(!Swappable<int, int>); // Wrong, int is definately swappable!
    return 0;
}
Tory answered 9/2, 2023 at 9:12 Comment(2)
@Adriaan I updated and provide one runnable answer here. and I think we could discuss this swap concept here, you see the cppreference code actually provides one answer and I attach it to here.Tory
Your example works because it just uses swap from the std namespace, but a 3rd party type implementing swap implements it in its own namespace (as you're not allowed to overload std::swap). So you need to use ADL by doing swap(...) rather than std::swap(...). But then std::swap needs to be visible from that scope. This whas the whole point of my question; how do we do that in a requires clause. Your answer doesn't answer it.Athalia

© 2022 - 2024 — McMap. All rights reserved.