Is it legit to define std::begin for const char*? - c++

I have a function for case insensitive comparison of strings which uses std::lexicographical_compare with custom comparator.
However i would like to be able to compare strings, string_views and const char* between each other, for maximum convenience and efficiency.
So i was thinking: What if i make a template, std::string has begin/end, std::string_view has begin/end, ... but const char* doesn't, not even in a form of non-member function.
So it is ok to define own begin/end overloads like this
namespace std {
const char * begin(const char* str) { return str; }
const char * end(const char* str) { return str + strlen(str); }
}
so that then i can compare everything with everything by
std::lexicographical_compare(std::begin(a), std::end(a), std::begin(b), std::end(b), icomp );
?
If not, how else could i solve my problem?

No, this is not legal, because const char * is not a user-defined type.
The behavior of a C++ program is undefined if it adds declarations or
definitions to namespace std or to a namespace within namespace std
unless otherwise specified. A program may add a template
specialization for any standard library template to namespace std only
if the declaration depends on a user-defined type and the
specialization meets the standard library requirements for the
original template and is not explicitly prohibited
[namespace.std/1]
You can instead declare those in some other namespace, such as ::
const char * begin(const char* str) { return str; }
const char * end(const char* str) { return str + strlen(str); }
And use them with unqualified calls
std::lexicographical_compare(begin(a), end(a), begin(b), end(b), icomp );
Additionally, in C++20, it will be even more restrictive, permitting only class templates specialisations for program-defined types
Unless otherwise specified, the behavior of a C++ program is undefined
if it adds declarations or definitions to namespace std or to a
namespace within namespace std.
Unless explicitly prohibited, a program may add a template
specialization for any standard library class template to namespace
std provided that (a) the added declaration depends on at least one
program-defined type and (b) the specialization meets the standard
library requirements for the original template.
[namespace.std]

Related

Overloading std::begin() and std::end() for non-arrays

Let's assume I have the following Data class:
struct Data {
char foo[8];
char bar;
};
and the following function, my_algorithm, which takes a pair of char * (similar to an STL algorithm):
void my_algorithm(char *first, char *last);
For Data's foo data member, instead of calling my_algorithm() like this:
Data data;
my_algorithm(data.foo, data.foo + 8);
I can use the std::begin() and std::end() convenience function templates:
my_algorithm(std::begin(data.foo), std::end(data.foo));
I would like to achieve something similar to Data's bar data member. That is, instead of writing:
my_algorithm(&data.bar, &data.bar + 1);
I would like to write something like:
my_algorithm(begin(data.bar), end(data.bar));
Therefore, I've defined the two following ordinary (non-template) functions for this case:
char* begin(char& c) { return &c; }
char* end(char& c) { return &c + 1; }
So that I would be able to write code like the following:
Data data;
using std::begin;
using std::end;
my_algorithm(begin(data.foo), end(data.foo)); // ok - std::begin()/std::end()
my_algorithm(begin(data.bar), end(data.bar)); // Error!!!
With the using declarations above I would have expected std::begin()/std::end() and ::begin()/::end() to be in the same overload set, respectively. Since the functions ::begin() and ::end() are a perfect match for the latter call and they are not templates, I would have expected the last call to my_algorithm() to match them. However, the ordinary functions are not considered at all. As a result the compilation fails, because std::begin() and std::end() are not matches for the call.
Basically, the latter call acts as if I had written instead:
my_algorithm(begin<>(data.bar), end<>(data.bar));
That is, only the function templates (i.e., std::begin()/std::end()) are considered by the overload resolution process, not the ordinary functions (i.e., not ::begin()/::end()).
It only works as expected, if I fully qualify the calls to ::begin()/::end():
my_algorithm(::begin(data.bar), ::end(data.bar));
What am I missing here?
Let's get a complete, reproducible example:
#include <iterator>
char* begin(char& c) { return &c; }
char* end(char& c) { return &c + 1; }
namespace ns {
void my_algorithm(char *first, char *last);
void my_function() {
using std::begin;
using std::end;
char c = '0';
my_algorithm(begin(c), end(c));
}
}
When you make the unqualified call to begin(c) and end(c), the compiler goes through the process of unqualified name lookup (described on the Argument-dependent lookup page of cppreference).
For regular unqualified name lookup, the process is roughly to start at the namespace you are currently in—::ns in this case—and only move out a namespace if you don't find the specific name.
If a function call is unqualified, as it is here with begin(c) and end(c), argument dependent lookup can occur, which finds free functions declared in the same namespace as the types of the functions' arguments, through the process of extending the overload set by finding "associated namespaces."
In this case, however, char is a fundamental type, so argument dependent lookup doesn't allow us to find the global ::begin and ::end functions.
For arguments of fundamental type, the associated set of namespaces and classes is empty
cppreference: argument dependent lookup
Instead, as we already have using std::begin; using std::end;, the compiler already sees possible functions for begin(...) and end(...)—namely those defined in namespace ::std—without having to move out a namespace from ::ns to ::. Thus, the compiler uses those functions, and compilation fails.
It's worth noting that the using std::begin; using std::end; also block the compiler from finding the custom ::begin and ::end even if you were to place them inside ::ns.
What you can do instead is write your own begin and end:
#include <iterator>
namespace ns {
char* begin(char& c) { return &c; }
char* end(char& c) { return &c + 1; }
template <typename T>
auto begin(T&& t) {
using std::begin;
// Not unbounded recursion if there's no `std::begin(t)`
// or ADL `begin(t)`, for the same reason that our
// char* begin(char& c); overload isn't found with
// using std::begin; begin(c);
return begin(t);
}
template <typename T>
auto end(T&& t) {
using std::end;
return end(t);
}
void my_algorithm(char *first, char *last);
void my_function() {
char c = '0';
my_algorithm(ns::begin(c), ns::end(c));
}
}
The title of question is "Overloading std::begin()". Overloading is possible only within the same scope. That is you can't overload names from different scopes. In another scope we can only make efforts to help lookup name. Essentially, here "using std::begin" declaration hides ::begin in question's code. See S.Lippman for reference:
functions that are members of two distinct namespaces do not overload one another.
Scope of a using Declaration. Names introduced in a using declaration obey normal scope rules.
Entities with the same name defined in an outer scope are hidden.
As soon as parameter is char and char is fundamental type - argument dependent lookup should not be taken into consideration - as mentioned in comments - there is no associated namespace with fundamental types.
Again, the question was: "What am I missing?" - therefore the answer is focused only on reasons - recommendations may be too broad.

redefine < operator to use in stl algorithms for strings

Is it possible to redefine operator < for strings without modifying std namespace, to make this operator use in standard algorithms?
For example, I can write:
namespace std
{
bool operator <(const std::string & rhs, const std::string & lhs)
{
std::cout << "lol";
return false;
}
}
int main()
{
std::vector<std::string> lol = { "a", "b", "ba", "aa" };
std::sort(lol.begin(), lol.end());
}
and "lol" will be printed several times. But if I move operator < outside from std namespace, default operator < will be used and nothing will be printed. Is it possible to make std::sort using custom operator < without including it to std namespace?
Yes I know, I can pass another comparator to std::sort but it's interesting for me if I could do what I asked and how?
Also am I correct, that it's correct to add such template specialization to std namespace?
Update: This is not practical question, I just want to know how can I do that if it's possible.
No, it is not. Adding a function to the standard namespace is undefined behavior. [namespace.std]/1 states:
The behavior of a C++ program is undefined if it adds declarations or definitions to namespace std or to a namespace within namespace std unless otherwise specified. A program may add a template specialization for any standard library template to namespace std only if the declaration depends on a user-defined type and the specialization meets the standard library requirements for the original template and is not explicitly prohibited.
If you want to change how std::sort sorts then you can provide a lambda and define what you want
std::sort(std::begin(foo), std::end(foo), [](const auto& lhs, const auto& rhs) { /* your code here */ });
Is it possible to redefine operator < for strings without modifiying std namespace
You can define the overload in another namespace, sure. But as you have found out, it will not be found by overload resolution unless explicitly qualified.
Is it possible to make std::sort using custom operator < without including it to std namespace?
Yes, and you already seem to know how:
Yes I know, I can pass another comparator to std::sort
This is exactly what the comparator argument is for.
Also am I correct, that it's corect to add such template specialization to std namespace?
That is not a template specialization; It is a function definition and you may not add function definitions to std namespace - or else the behaviour is undefined. You would be allowed to add template specializations, but only if at least one type argument is a user defined type.

Overloading of operator[] in a template

I'm trying to overload operator[] for an std::map instance, and seriously puzzled by the compilation errors from GCC.
The following example will not compile:
typedef std::map< int*, int > mymap;
namespace std {
template <>
int & mymap::operator[]( const int* & k) {
return begin()->second;
};
};
This one fails with:
error: template-id 'operator[]<>' for 'int& std::map, std::allocator > >::operator[](const int*&)' does not match any template declaration
But if you replace int* with myintp (typedef int* myintp) it will compile just fine.
It's also interesting why template<> and namespace are needed here.
Update:
I oversimplified the example.
It is allowed to add template specializations for any standard library
template to the namespace std only if the declaration depends on a
user-defined type and the specialization satisfies all requirements
for the original template, except where such specializations are
prohibited.
typedef std::map< myclass*, int > mymap;
namespace std {
template <>
int & mymap::operator[]( myclass* const & k) {
return begin()->second;
};
};
Would that example provide a legal and predictable behavior?
From 17.6.4.2.1/2:
The behavior of a C++ program is undefined if it declares
— an
explicit specialization of any member function of a standard library
class template, or
So right there all bets are off and the compiler has no obligation to compile your code (other answers show why the compiler appears to accept the code that uses the typedef, but that still doesn't make it legal).
C++98 is slightly less explicit, in 17.4.3.1/1:
It is undefined for a C++ program to add declarations or definitions
to namespace std or namespaces within namespace std unless otherwise
specified. A program may add template specializations for any standard
library template to namespace std. Such a specialization (complete or
partial) of a standard library template results in undefined behavior
unless the declaration depends on a user-defined name of external
linkage and unless the specialization meets the standard library
requirements for the original template.
Now, it's somewhat unclear if this strictly prohibits specializing a member of a template of std namespace (vs a full or partial specialization of the container template itself) but your particular code is certainly undefined because it doesn't specialize on a user-defined name of external linkage.
The key type is int*.
In order to match
T& operator[]( const Key& key );
you need to use:
int & mymap::operator[]( int* const & k) {
Unfortunately, use of const before the type is confusing. Had the declaration been
T& operator[]( Key const& key );
it would have been easier to come up with the right argument declaration for what you are trying.
Let's analyze your code:
int& mymap::operator[](const int* &k)
{
return begin ()->second;
};
This is wrong since the parameter is a reference to a pointer to a const int, not a reference to a const pointer to an int.
The position of const have confused you, so rewrite it as:
int& mymap::operator[](int *const &k)
{
return begin ()->second;
};

How to specialize std::begin?

I'm trying to specialize std::begin for a custom container. I'm doing this because I want to use range-based for with the container. This is what I have:
class stackiterator { … };
class stack { … };
#include <iterator>
template <> stackiterator std::begin(stack& S)
{
return S.GetBottom();
}
I get the following error at the definition of my begin specialization:
No function template matches function template specialization 'begin'
What am I doing wrong?
I'm trying to specialize std::begin for a custom container. I'm
doing this because I want to use range-based for with the container.
You are barking up the wrong tree. Range-based for does not use std::begin at all. For class types, the compiler looks up member begin and end directly, and if neither is found, does an ADL-only lookup for free begin and end in the associated namespaces. Ordinary unqualified lookup is not performed; there's no way for std::begin to be picked up if your class is not in the std namespace.
Even if the specialization you want to do is possible (it isn't unless you introduce a member begin() - an explicit specialization for a function template can't change the return type, and the overload at issue returns "whatever member begin() returns"; and if you do introduce a member begin(), why are you specializing std::begin to do what it would have done anyway?), you still won't be able to use it with a range-based for.
Leaving aside the policy and semantic issues of whether you should specialize a function template from the std namespace,
The following snippet does not work:
class stackiterator {};
struct stack { stackiterator Begin() { return stackiterator{};} };
#include <iterator>
namespace std
{
template <> stackiterator begin<stack>(stack& S)
{
return S.Begin();
}
}
However, the following snippet works just fine:
class stackiterator {};
struct stack { stackiterator begin() { return stackiterator{};} };
#include <iterator>
namespace std
{
template <> stackiterator begin<stack>(stack& S)
{
return S.begin();
}
}
The key difference is the presence of Begin() vs begin() as a member function of stack. std::begin() is defined as:
template <class C> auto begin(C& c) -> decltype(c.begin());
template <class C> auto begin(const C& c) -> decltype(c.begin());
When you specialize a function template, you must still keep the return type the same. When you don't have begin() as a member of Stack, the compiler does not know how to determine the return type.
That is the reason for error produced by the compiler.
BTW, there is another SO post that partially answers what can be specialized and what can't be specialized.
Looking at the part of the standard that deals with std::begin(), Section 24.3, I don't see anything about not being able to specialize std::begin().
The right way to add a free function begin that enables for(:) loops is to add, in the namespace of stack, a begin(stack&) and begin(stack const&) function that returns a iterator and const_iterator respectively (and ditto for end)
The other way is to add a member begin() and end() to stack.
Specializing std::begin is bad practice for a number of reasons, not the least of which is that not all for(:) loops will work with it (the lookup rules where changed in the resolution of this defect report). Overloading std::begin is undefined behavior (you may not overload functions in namespace std under the standard: doing so makes your program ill-formed).
This is how it has to be done, even if it violates the naming convention of your project.

Can I specialize std::begin and std::end for the return value of equal_range()?

The <algorithm> header provides std::equal_range(), as well as some containers having it as a member function. What bothers me with this function is that it returns a pair of iterators, making it tedious to iterate from the begin iterator to the end iterator. I'd like to be able to use std::begin() and std::end() so that I can use the C++11 range-based for-loop.
Now, I've heard contradictory information in regards to specializing std::begin() and std::end() - I've been told that adding anything to the std namespace results in undefined behavior, whereas I have also been told that you can provide your own specializations of std::begin() and std::end().
This is what I am doing right now:
namespace std
{
template<typename Iter, typename = typename iterator_traits<Iter>::iterator_category>
Iter begin(pair<Iter, Iter> const &p)
{
return p.first;
}
template<typename Iter, typename = typename iterator_traits<Iter>::iterator_category>
Iter end(pair<Iter, Iter> const &p)
{
return p.second;
}
}
And this does work: http://ideone.com/wHVfkh
But I am wondering, what are the downsides to doing this? Is there a better way to do this?
17.6.4.2.1/1 The behavior of a C++ program is undefined if it adds declarations or definitions to namespace std or to a namespace
within namespace std unless otherwise specified. A program may add a
template specialization for any standard library template to namespace
std only if the declaration depends on a user-defined type and the
specialization meets the standard library requirements for the
original template and is not explicitly prohibited.
So yes, I believe that, technically, your code exhibits undefined behavior. Perhaps you can write a simple class that takes a pair of iterators in its constructor and implements begin() and end() methods. Then you can write something like
for (const auto& elem: as_range(equal_range(...))) {}