Movatterモバイル変換


[0]ホーム

URL:


Retiring niebloids

Document #:P3136R1
Date:2024-11-18
Project:Programming Language C++
Audience: LWG
Reply-to: Tim Song
<t.canens.cpp@gmail.com>

1 Abstract

This paper proposes that we respecify the algorithms instd::ranges that are currently so-called niebloids to be actual function objects.

2 Revision history

3 Background

“Niebloid” is the informal name given to the “function templates” instd::ranges with the special property described in26.2[algorithms.requirements] p2:

2 The entities defined in thestd​::​ranges namespace in this Clause are not found by argument-dependent name lookup (6.5.4[basic.lookup.argdep]). When found by unqualified (6.5.3[basic.lookup.unqual]) name lookup for thepostfix-expression in a function call (7.6.1.3[expr.call]), they inhibit argument-dependent name lookup.

Example 1:
void foo(){usingnamespace std::ranges;  std::vector<int> vec{1,2,3};  find(begin(vec), end(vec),2);// #1}

The function call expression at #1 invokesstd​::​ranges​::​find, notstd​::​find, despite that (a) the iterator type returned frombegin(vec) andend(vec) may be associated with namespacestd and (b)std​::​find is more specialized (13.7.7.3[temp.func.order]) thanstd​::​ranges​::​find since the former requires its first two parameters to have the same type. — end example ]

This example also shows the reason for this requirement: because ranges algorithms accept iterator-sentinel pairs, they are almost always less specialized than their classic counterparts and therefore would otherwise lose overload resolution.

Of course, there’s nothing in the core language that actually allow afunction template to have this property. Instead, all major implementations implement them as function objects, aided by the ban on explicit template argument lists in26.2[algorithms.requirements] p15:

15 The well-formedness and behavior of a call to an algorithm with an explicitly-specified template argument list is unspecified, except where explicitly stated otherwise.

The original idea behind this specification was that there might eventually be a core language change and/or some compiler extension that would allow implementing them as function templates.

4 Discussion

We should bite the bullet and just specify niebloids as function objects.

4.1 No language extension has materialized

Four years after C++20, the language extension has not materialized, and is unlikely to do so. It is hard to motivate a language change when the function-object based implementation works just as well.

4.2 Implementations have converged

While there were originally implementation divergence on whether niebloids should be CPO-likesemiregular function objects or noncopyable ones to more closely emulate function templates, the major implementationshave now converged on semiregularity. We should standardize this existing practice.

4.3 The status quo discourages reasonable code

Consider:

auto x= std::views::transform(std::ranges::size);auto y= std::views::transform(std::ranges::distance);auto z= std::views::transform(std::ref(std::ranges::distance));auto w= std::views::transform([](auto&& r){return std::ranges::distance(r);});

x is valid becausesize is a CPO.y might not be, becausedistance is a niebloid, and until last October, at least one major implementation disallowed copying niebloids.z is de-facto valid - as long asstd::ranges::distance is an object,std::ref should work on it, and thenreference_wrapper’s call operator kicks in.w is valid but excessively verbose.

There’s nothing inherently objectionable abouty; we are not doing our users a service by disallowing it on paper. There’s no reason to insist on writing a lambda.

4.4 Unresolved wording issues

The difference between a function object and a set of function templates goes beyond the suppression of ADL and the inability to specify a template argument list. For example, function objects do not overload. The current wording is therefore technically insufficient to permit the function-object implementation. Instead of trying to further weasel-word this, I think we should just admit that niebloids are function objects.

5 Wording

This wording is relative to[N4971].

  1. Strike16.3.3.3.5[customization.point.object] p6:

[ Drafting note: This sentence isn’t really true - what concept doesviews::single constrain its return type with? In any event, it doesn’t have any normative effect on its own; if there is a constraint then the CPO’s specification will specify it. ]

6 Each customization point object type constrains its return type to model a particular concept.

  1. Add the following subclause to16.3.3[conventions]:

16.3.3.? Algorithm function objects [niebloid]

1 Analgorithm function object is a customization point object (16.3.3.3.5[customization.point.object]) that is specified as one or more overloaded function templates. The name of these function templates designates the corresponding algorithm function object.

2 For an algorithm function objecto, letS be the corresponding set of function templates. Then for any sequence of argumentsargs...,o(args...) is expression-equivalent tos(args...), where the result of name lookup fors is the overload setS.

Note 1:Algorithm function objects are not found by argument-dependent name lookup (6.5.4[basic.lookup.argdep]). When found by unqualified (6.5.3[basic.lookup.unqual]) name lookup for thepostfix-expression in a function call (7.6.1.3[expr.call]), they inhibit argument-dependent name lookup.

Example 1:
void foo() {  using namespace std::ranges;  std::vector<int> vec{1,2,3};  find(begin(vec), end(vec), 2);        // #1}

The function call expression at #1 invokesstd​::​ranges​::​find, notstd​::​find. — end example ]

— end note ]
  1. Edit24.4.4.1[range.iter.ops.general] as indicated:

2 The function templates defined in24.4.4[range.iter.ops] are not found by argument-dependent name lookup (6.5.4[basic.lookup.argdep]). When found by unqualified (6.5.3[basic.lookup.unqual]) name lookup for thepostfix-expression in a function call (7.6.1.3[expr.call]), they inhibit argument-dependent name lookup.

Example -2:
void foo() {    using namespace std::ranges;    std::vector<int> vec{1,2,3};    distance(begin(vec), end(vec));     // #1}

The function call expression at #1 invokesstd​::​ranges​::​distance, notstd​::​distance, despite that (a) the iterator type returned frombegin(vec) andend(vec) may be associated with namespace std and (b)std​::​distance is more specialized (13.7.7.3[temp.func.order]) thanstd​::​ranges​::​distance since the former requires its first two parameters to have the same type. — end example ]

3 The number and order of deducible template parameters for the function templates defined in24.4.4[range.iter.ops] is unspecified, except where explicitly stated otherwise.

2 The entities defined in24.4.4[range.iter.ops] are algorithm function objects (16.3.3.? [niebloid]).

  1. Edit26.2[algorithms.requirements] p2 as indicated:

2 The entities defined in thestd​::​ranges namespace in this Clause are not found by argument-dependent name lookup (6.5.4[basic.lookup.argdep]). When found by unqualified (6.5.3[basic.lookup.unqual]) name lookup for thepostfix-expression in a function call (7.6.1.3[expr.call]), they inhibit argument-dependent name lookup.

Example -1:
void foo() {  using namespace std::ranges;  std::vector<int> vec{1,2,3};  find(begin(vec), end(vec), 2);        // #1}

The function call expression at #1 invokesstd​::​ranges​::​find, notstd​::​find, despite that (a) the iterator type returned frombegin(vec) andend(vec) may be associated with namespacestd and (b)std​::​find is more specialized (13.7.7.3[temp.func.order]) thanstd​::​ranges​::​find since the former requires its first two parameters to have the same type. — end example ]

2 The entities defined in thestd​::​ranges namespace in this Clause and specified as function templates are algorithm function objects (16.3.3.? [niebloid]).

6 References

[N4971] Thomas Köppe. 2023-12-18. Working Draft, Programming Languages — C++.
https://wg21.link/n4971


[8]ページ先頭

©2009-2025 Movatter.jp