34

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?

fdermishin
  • 3,519
  • 3
  • 24
  • 45
Stick it to THE MAN
  • 5,621
  • 17
  • 77
  • 93

5 Answers5

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.

JonM
  • 830
  • 8
  • 10
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).

David Rodríguez - dribeas
  • 204,818
  • 23
  • 294
  • 489
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.

JoeG
  • 12,994
  • 1
  • 38
  • 63
  • 3
    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 ;-) – Steve Jessop Feb 17 '10 at 21:47
  • There's been much discussion on the boost mailing lists about which is best, which is why boost::swap picks up either. – JoeG Feb 17 '10 at 22:02
  • 3
    `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. – Erik Schnetter Nov 07 '15 at 00:38
  • @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. – JoeG Dec 07 '15 at 15:52
  • 1
    @JoeGauterin says: "The partial specialization of std::less for any pointer type yields a total order, even if the built-in operator< does not." – Erik Schnetter Dec 08 '15 at 17:10
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 
abhiarora
  • 9,743
  • 5
  • 32
  • 57
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...
}
davidnr
  • 3,443
  • 2
  • 19
  • 14
  • 1
    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'?` – v.oddou Apr 16 '20 at 08:58
  • 1
    Yes, has to be in std::. either wrap it in std:: or use std::less as mentioned above. – Kobi Jan 29 '22 at 19:49