0

As we know, the CPO can use concepts to check the performance requirements(like input type, output type, etc.) of the user-defined function reload that it found. But they can't check the semantics of the user's function, right? Like the following codes show:

namespace My_A{
    struct A{};
    void some_name(A);//not fit
}

namespace My_B{
    struct B{};
    int some_name(B);//fit, but do unrelated matters
}

template<typename T>
concept usable_some_name =
requires(T arg) {
    some_name(arg) -> int;
};

struct __use_fn{
    template<typename T>
    int operator()(T a)
    {
        if constexpr (usable_some_name<T>)
            return some_name(a);
        // ...
    }
};

__use_fn use{};

It can weed out unqualified functions, but there is no guarantee that a "qualified" function will do what it is expected to do. If I want to make sure my code is error-free, either I have to circumvent the names' use of all the possible touched CPO used (which is impossible), or I have to make sure that my function of the same name (if any) has the semantics that those CPO expects (which also seems like an unreasonable burden) when all the CPO may be used.

Is that an unreasonable request that I must define my function to behave the same way as the ranges algorithm wanted every time they have just the same name that the ranges algorithm uses?

Compared to the traditional method:

template <>
struct hash<my_type>
{
    // ...
}

or

priority_queue<my_type, vector<my_type>, my_type_greater> pq;

We can easily find that in those traditional ways we can make our customization be called only when we clearly want, without any risk of miscall as CPO made.

Is the current CPO bringing something that is an over-coupling? (I know what it is designed for and what its advantages are, though) Is there any better way (maybe tag_invoke or what, I don't know) to resolve this problem?

zclll
  • 77
  • 7
  • 3
    Unrelated to your problem, but note that all symbols beginning with double underscore are reserved, and may not be defined in your own code. See e.g. [What are the rules about using an underscore in a C++ identifier?](https://stackoverflow.com/questions/228783/what-are-the-rules-about-using-an-underscore-in-a-c-identifier) for more details. – Some programmer dude Oct 29 '22 at 21:25
  • I think these "has one specific function with a certain name and type" are generally considered "bad" concepts. A concept should represent a domain of types, e.g. "arithmetic" or "copyable" or "regular". – JHBonarius Oct 29 '22 at 21:26
  • As for the question itself, i'm not quite sure I understand the underlying problem... Do you have name collisions, like using a symbol with the same name as one in the standard library? Or that there's no requirement for symbol names and their implementation, so a function named `add` could effectively do a subtraction? – Some programmer dude Oct 29 '22 at 21:30
  • "*which also seems like an unreasonable burden*" That sounds like a problem with your names. – Nicol Bolas Oct 29 '22 at 21:32
  • i must have missed something in my c++ reading - but what is a CPO? – Neil Butterworth Oct 29 '22 at 21:39
  • @Someprogrammerdude Use `add` to do a subtraction is obviously not reasonable. But similar scenarios are possible. Especially when CPO is applied to third-party libraries more and more widely, not just stl. The `begin` that returns an iterator may actually be the iterator that initiated some transaction and then returned the result. If `begin` is a name that programmers should be wary of, what if the CPO name is used more and more? – zclll Oct 29 '22 at 21:39
  • @Someprogrammerdude See, just like `begin`, if when we use some algorithm that involves CPO we need to be alert to whether the name has a special meaning and will be used for a particular purpose, this is what I mean by "unreasonable burden". – zclll Oct 29 '22 at 21:41
  • @zclll: "*The begin that returns an iterator may actually be the iterator that initiated some transaction and then returned the result.*" Why would you call a function that returns a type that satisfies an iterator concept "begin" if it doesn't represent what the "begin" function is supposed to mean? And in order for `ranges::begin` to work on it, it would also have to have an "end" function that just so happens to return a valid sentinel against that range. That's a pretty big coincidence, isn't it? – Nicol Bolas Oct 29 '22 at 21:41
  • @NicolBolas Great. That's just what I want to talk about: This brings with it exactly a general new burden - we need to make our name use have a specific meaning that has absolutely nothing to do with grammar. If `begin` should be, how about thousands of other words? – zclll Oct 29 '22 at 21:44
  • @zclll: "*we need to make our name use have a specific meaning that has absolutely nothing to do with grammar.*" ... what are you talking about? – Nicol Bolas Oct 29 '22 at 21:45
  • @NeilButterworth [What is a niebloid?](https://stackoverflow.com/questions/62928396/what-is-a-niebloid) also contains a definition of CPO. I had to search for it myself. :) – Some programmer dude Oct 29 '22 at 21:47
  • @NicolBolas `begin` is just an example, and what I want to use it to illustrate is this: if we need to consider one such word, you may find it reasonable, but is it reasonable that there is one and then there is two, and then there is two and then there is three ...... The use of CPO leads to new restrictions on identifiers (not syntax). Is it still reasonable? – zclll Oct 29 '22 at 21:48
  • I’m voting to close this question because it's *too* narrow, leading to to long discussions rather than producing answers. – Some programmer dude Oct 29 '22 at 21:51
  • @zclll: "*The use of CPO leads to new restrictions on identifiers (not syntax). Is it still reasonable?*" I think your concerns about restrictions on identifiers are highly overblown and speculative. – Nicol Bolas Oct 29 '22 at 21:53
  • @zclll I don't see function object `use` change anything here than normal template function `use` – apple apple Oct 29 '22 at 21:57
  • @NicolBolas I don't think so. The CPO will be more and more widely used. I admit that the probability of what I fear is very low, but please note that when this pattern is widely used in third-party libraries, there is a risk of misuse of very common terms, and the resulting errors are very difficult to notice. – zclll Oct 29 '22 at 21:58
  • 1
    @zclll: "*there is a risk of misuse of very common terms*" When I told the doctor that it hurts when I raise my arm like that, she said "then don't raise your arm like that." Nobody is *forcing you* to use "very common terms" for your extension method names. – Nicol Bolas Oct 29 '22 at 22:15
  • 1
    hmm, sometimes this all seems a bit lovecraftian – Neil Butterworth Oct 29 '22 at 22:50
  • @NicolBolas Don't you think the restrictions on the use of "_very common terms_" gives them some syntactic responsibility beyond the identifier? Do you think it's reasonable? – zclll Oct 29 '22 at 22:57
  • @zclll: I don't know what "syntactic responsibility" means. All I know is that you have the tools needed to avoid the problem if you're concerned about it. – Nicol Bolas Oct 29 '22 at 23:00
  • @NicolBolas I looked up the information and found that my concerns were not surprising. Almost all analyses of CPO list this overuse of names as its main flaw. I'll write a reply comparing them after I learn about the tag_invoke scheme. – zclll Oct 30 '22 at 09:44

1 Answers1

1

Customization points have a number of built-in mechanisms that reduce the chance of an accidental match substantially.

Let us consider ranges::begin(t). This works on a type T which is either:

  • An array
  • Has a member begin
  • Has an ADL-visible begin non-member function accessible via begin(t)

In the latter two cases, the selected begin function must

  • Take no parameters other than t.
  • Return an object which matches input/output iterator.

Therefore, in order for there to be a collision against ranges::begin, some other CPO must do all of the following:

  • The CPO must choose the incredibly non-descript word "begin" for the name of the extensible function it is looking for.
  • This function must take zero other parameters.
  • This function must return a value for which a type matching input/output iterator could be a legitimate return type.

The last two in particular are kind of... rare. I mean even for "begin", what is really the likelihood that someone would use begin as a nullary extension function and its return value would legitimately be an iterator without actually being the first element of a range? That sounds vanishingly unlikely.

Now yes, the standard library CPOs do have the advantage of being... standard. Everybody knows that begin/end are for iterator ranges. So nobody would actually use them for CPOs.

But nobody's forcing you to use one-word names for these extension functions. The standard library does it, but you can use my_functionality_extension_method_name if it makes you feel safer about conflicts. Your CPO can still be named my_namespace::method_name. Indeed, you can have the member version be method_name while the non-member version is longer. Or whatever works for you.

Speaking of which, you need to keep in mind is the distinction between having a CPO and what the CPO does. A CPO exists to keep people from specifically overloading or specializing the function (and from using ADL to invoke it). That's why it is a function object instead of a regular function.

But nothing about being a CPO dictates how you extend them. ranges::data isn't based on whether you have an accessible data function. It instead works through std::to_address, which goes through pointer traits or a user-provided specialization.

So if you would rather use some other method for users to extend a CPO, like a traits class or whatever, you are free to do so. The CPO is just a mechanism for invoking that functionality.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982