Specialization of 'template<class _Tp> struct std::less' in different namespace
Asked Answered
C

5

35

I am specializing the 'less' (predicate) for a data type.

The code looks like this:

template<>
struct std::less<DateTimeKey>
{
   bool operator()(const DateTimeKey& k1, const DateTimeKey& k2) const
   {
      // Some code ...
   }
};

When compiling (g++ 4.4.1 on Ubuntu 9.10), I get the error:

Specialization of 'template struct std::less' in different namespace

I did some research and found that there was a 'workaround' which involved wrapping the specialization in a std namespace - i.e. changing the code to:

namespace std {
template<>
struct less<DateTimeKey>
{
   bool operator()(const DateTimeKey& k1, const DateTimeKey& k2) const
   {
      // Some code ...
   }
};
}

which indeed, shuts the compiler up. However, that solution was from a post 5 years old (By the 'great' Victor Bazarof no less [pun unintended]). Is this fix still the way to go, or is there a better way of resolving this, or is the "old way" still valid?

Cranford answered 17/2, 2010 at 16:19 Comment(1)
Overload DateTimeKey::operator<?Gesticulate
V
30

This is still the way to do it. Unfortunately you cannot declare or define functions within a namespace like you would do with a class: you need to actually wrap them in a namespace block.

Vaporish answered 17/2, 2010 at 16:22 Comment(0)
T
26

If you need to specialize a standard algorithm, you can do so in the std namespace. It is the only thing that you are allowed to do inside that namespace according to the standard.

[lib.reserved.names]/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, the question is whether you actually want to specialize std::less. Note that std::less will call the comparison operator defined for your type, so you can provide that operation instead of specializing the template.

The problem with specializing std::less for your particular type is that it will cause confusion if you provide a different operation than the one performed by operator< for your type. If they perform the same operation, just leave the default std::less definition without specialization.

If you do not want to provide the comparison operator, but still want to use the type in associative containers or with algorithms that require a comparator, you can provide an external comparison functor by other name that will not confuse other readers (and yourself somewhere in the future).

Tristantristas answered 17/2, 2010 at 16:50 Comment(0)
C
4

Why are you even doing this?

std::less exists for two purposes only:

  1. to give a name to operator <, allowing it to be passed as a functor
  2. to explicitly allow comparing two pointers that aren't in the same array (which is technically illegal if done with raw pointers)

There's no reason for a user to overload it - either overload operator< or use a custom comparator function.

There are std algorithms that can be sensibly specialized - std::swap is a good example - and to do so you do need to declare the specialization inside namespace std.

Crayon answered 17/2, 2010 at 17:7 Comment(6)
I'm not sure std::swap is a good example, since the smart money is that you don't specialize it, you define an independent swap function in the same namespace as the UDT, and let ADL find it. The exception I can think of is if your class has to work with a template which never got that memo, and which calls std::swap(t1,t2) instead of using std::swap; swap(t1,t2);. Not sure whether this is sensible specialization, or merely pragmatic specialization ;-)Pharisee
There's been much discussion on the boost mailing lists about which is best, which is why boost::swap picks up either.Crayon
std::less exists for a third reason: Sometimes, a type's operator< behaves slightly strangely. The prime example is floating point numbers where one may want to treat -0.0 different from 0.0, or where one may want to make nan comparable. Other examples are types that only have a partial order, and where the < notation is a natural domain-specific notation. In these cases, specializing std::less allows using this type in sets and maps.Jinnyjinrikisha
@Erik Schnetter: In those cases you should use a custom comparator. Specializing std::less to not behave like operator < vioaltes the principle of least surprise.Crayon
@JoeGauterin <en.cppreference.com/w/cpp/utility/functional/less> says: "The partial specialization of std::less for any pointer type yields a total order, even if the built-in operator< does not."Jinnyjinrikisha
I have had the case where I want more than one map sorted in different ways and being able to specify a "custom" comparitor is is desirable for this.Austroasiatic
H
3

Even though the question has been answered by others with answers on how to specialize std::less (by wrap them in a namespace block) and the right way to do it ( to overload operator <).

However, C++ now allows (in C++11) to speciliaze the way you did in your first example.

An explicit specialization shall be declared in a namespace enclosing the specialized template. An explicit specialization whose declarator-id is not qualified shall be declared in the nearest enclosing namespace of the template, or, if the namespace is inline (7.3.1), any namespace from its enclosing namespace set. Such a declaration may also be a definition. If the declaration is not a definition, the specialization may be defined later (7.3.1.2).

I tried the following code with g++ (8.3.0) on my Ubuntu machine.

#include <iostream>

#include <map>
#include <string>
#include <algorithm>


class myType {

public:
    myType(int in): i_(in) { }

    int i_;
};

template <>
struct std::less<myType>  {
    bool operator()(const myType& a, const myType& b) const
    {
        return a.i_ < b.i_;
    }
};


int main(int argc, char *argv[])
{
    std::map<myType, std::string> vector = { { 1, "1"}, { 2, "2"}, { 3, "3"}, { 0, "0" } };

    for (auto& i: vector)
        std::cout << i.first.i_ << std::endl;
    return 0;
}

The above code was compiled with

g++ --std=c++11 compare.cpp -Wall 
Harsho answered 16/11, 2019 at 10:47 Comment(0)
L
1

The less functor doesn't have to be in std namespace. So

struct A
{
    A(int _v=0):v(_v){}
    int v;
};


template<>  struct less<A>
{
    bool operator()(const A& k1, const A& k2) const
    {
        return k1.v < k2.v;
    }
};


std::map<A,int> m;
m[A(1)] = 1;
m[A(2)] = 2;

Works as expected. (Calls the functor you just created).

I guess you already know, but you can just write your own operator<(k1,k2), which is what default less functor looks for.

bool operator<(const DateTimeKey & k1, const DateTimeKey & k2)
{
//your code...
}
Laurynlausanne answered 17/2, 2010 at 16:35 Comment(2)
nope, it does have to be in std. that less struct has no main template. clang says error: no template named 'less'; did you mean 'std::less'?Psychic
Yes, has to be in std::. either wrap it in std:: or use std::less as mentioned above.Kierkegaardian

© 2022 - 2024 — McMap. All rights reserved.