2

I have a class template that intends to use its parameter K as the key to a map.

Is there any way to restrict the template parameter to be a type that complies with the Key in std::map?

I realize that even without such constraint, the compiler would spit out a pile of template errors like K having no operator < (), but it would be nice if I can make my code more obvious in specifying requirements.

C++11 solutions are welcome.

template< typename K >
class Foo
{
  // lots of other code here...

  private:
    std::map< K, size_t > m_map;
};
Mat
  • 202,337
  • 40
  • 393
  • 406
kfmfe04
  • 14,936
  • 14
  • 74
  • 140
  • 1
    Possible Duplicate : http://stackoverflow.com/questions/148373/c-restrict-template-function If it is not the same question, it at least answers it. – Joe McGrath Dec 18 '11 at 02:42
  • What behavior is expected if the "K" is passed which doesn't go well with std::map? – iammilind Dec 18 '11 at 03:00
  • @JoeMcGrath +1 for a helpful link - too bad STL doesn't seem to have something like this: http://libcds.sourceforge.net/doc/cds-api/structcds_1_1map_1_1key__traits.html – kfmfe04 Dec 18 '11 at 10:00

4 Answers4

6

It depends on what you mean by "complies." If you want to verify that K has a < operator then you might try the Boost Concept Checking Library.

#include "boost/concept_check.hpp"

template< typename K >
class Foo
{
  BOOST_CONCEPT_ASSERT((boost::LessThanComparable< K >));

  // lots of other code here...

  private:
    std::map< K, size_t > m_map;
};

However if you want to verify that < defines a StrictWeakOrdering on K, that would require testing run-time behaviour under all possible inputs.

Andrew Durward
  • 3,771
  • 1
  • 19
  • 30
  • I believe that keys must also be copy contructable or moveable in C++11. – Omnifarious Dec 18 '11 at 06:37
  • @Omnifarious this is part of the problem - the requirements are not obvious without some previous magic knowledge (magic in the sense that it's not in the code - at least not obvious in the code). Also, Andrew's comment is pertinent: we still can't seem to know if we've implemented our custom Class's ordering properly at compile time: we still need to run some test to ensure that it's working right. – kfmfe04 Dec 18 '11 at 10:06
  • +1 to Andrew for mentioning Boost::Concepts - I guess this is one of the features that `didn't` make it into C++11... – kfmfe04 Dec 18 '11 at 10:17
  • The only problem is that you'd still get lots of "no `operator<`" errors, and then this brilliant error mentioning problems with LessThenComparable. – UncleBens Dec 18 '11 at 10:41
  • 1
    @kfmfe04 Yes, unfortunately Concepts were dropped from the new standard. See [ConceptGCC](http://www.generic-programming.org/software/ConceptGCC/) for an implementation (but I believe it's no longer maintained). – Andrew Durward Dec 18 '11 at 15:39
  • @AndrewDurward +1 tyvm for the tip - I will try it out! – kfmfe04 Dec 18 '11 at 15:49
  • @kfmfe04: And Boost::Concepts is not that feature. It is a (by necessity) kludgey attempt to imitate a part of the feature with existing compiler constructs. – Omnifarious Dec 19 '11 at 14:48
5

C++11 version:

#include <type_traits>

template<class T>
struct satisfies_key_req{
  struct nat{};

  template<class K> static auto test(K* k) -> decltype(*k < *k);
  template<class K> static nat  test(...);

  static bool const value = !std::is_same<decltype(test<T>(0)), nat>::value;
};

#include <iostream>

struct foo{};

int main(){
    static bool const b = satisfies_key_req<int>::value;
    std::cout << b << '\n';
    static bool const b2 = satisfies_key_req<foo>::value;
    std::cout << b2 << '\n';
}

Output:

1
0

The key point I used here is expression SFINAE: auto test(K* k) -> decltype(*k < *k). If the expression in the trailing-return-type is not valid, then this particular overload of test is removed from the overload set. In other words, it's SFINAE'd.

§14.8.2 [temp.deduct]

p6 At certain points in the template argument deduction process it is necessary to take a function type that makes use of template parameters and replace those template parameters with the corresponding template arguments. This is done at the beginning of template argument deduction when any explicitly specified template arguments are substituted into the function type, and again at the end of template argument deduction when any template arguments that were deduced or obtained from default arguments are substituted.

p7 The substitution occurs in all types and expressions that are used in the function type and in template parameter declarations. The expressions include not only constant expressions such as those that appear in array bounds or as nontype template arguments but also general expressions (i.e., non-constant expressions) inside sizeof, decltype, and other contexts that allow non-constant expressions.

p8 If a substitution results in an invalid type or expression, type deduction fails. An invalid type or expression is one that would be ill-formed if written using the substituted arguments. [...]


You can use it in three flavors for your Foo class to trigger an error.

// static_assert, arguably the best choice
template< typename K >
class Foo
{
  static_assert<satisfies_key_req<K>::value, "K does not satisfy key requirements");
  // lots of other code here...

  private:
    std::map< K, size_t > m_map;
};

// new-style SFINAE'd, maybe not really clear
template<
  typename K,
  typename = typename std::enable_if<
               satisfies_key_req<K>::value
             >::type
>
class Foo
{
  // lots of other code here...

  private:
    std::map< K, size_t > m_map;
};

// partial specialization, clarity similar to SFINAE approach
template< 
  typename K,
  bool = satisfies_key_req<K>::value
>
class Foo
{
  // lots of other code here...

  private:
    std::map< K, size_t > m_map;
};

template<typename K>
class Foo<K, false>;
Community
  • 1
  • 1
Xeo
  • 129,499
  • 52
  • 291
  • 397
  • +1 v.nice! I need some time to think about your solution to digest its implications... ...maybe improve this with static_assert() to ensure that we catch problems during compile time? – kfmfe04 Dec 18 '11 at 10:11
  • @kfmfe04: I only provided the type trait, if you use it in a `static_assert` or newstyle SFINAE, or even in a construct like shown in other answers (partial specialization) to trigger a compiler error is something for you to decide. :) All three would work, and I think `static_assert` is the clearest. Note that currently only Clang has the correct output, MSVC and the GCC versions I could test all don't trigger SFINAE on `-> decltype(k < k)` yet, even though it's legal and standard C++. I'll edit in some explanations soon. – Xeo Dec 18 '11 at 16:11
2

A complete solution to your problem probably is impossible. But I can suggest you a solution, if you want to restrict K to be a specific type you can define your class as follows

template <class K, bool = std::is_same<K,int>::value>
class Foo { ... };

template <class K>
class Foo<K,false> { Foo(); };

So if K is int your class works as expected. Otherwise you can not costruct object of type Foo<K>. Obviously you can refine the condition.

To check if K has the operator< you can use boost::has_less, but as you can check in the documentation this traits doesn't work properly always.

mattia.penati
  • 532
  • 5
  • 18
  • 3
    You can use `BOOST_STATIC_ASSERT` or C++11 `static_assert` instead of that template hack there – bdonlan Dec 18 '11 at 03:33
  • @bdonlan +1 for mentioning `static_assert`. +1 to mattia for mentioning `boost::has_less` and for adding elaboration link. – kfmfe04 Dec 18 '11 at 10:15
1

From your question, I guess you want one suitable error instead of loads of errors.

Following code finds, if a type has a valid operator < or not:

namespace OperatorExist
{
  typedef char no[3]; // '3' can be any awkward number
  template<typename T> no& operator < (const T&, const T&);

  template<typename T>
  struct LessThan {
    static const bool value = (sizeof(*(T*)(0) < *(T*)(0)) != sizeof(no));
  };
}

Now, you can specialize class Foo using the above construct:

template<typename K, bool = OperatorExist::LessThan<K>::value>
class Foo 
{
  // lots of other code here...
  private:
    std::map<K, size_t> m_map;
};

template<typename K>
class Foo<K, false>; // unimplemented for types not suitable for 'std::map'

As soon as you use some type A which doesn't have compatibility with std::map, the compiler will complain with single error:

error: aggregate ‘Foo<A> oa’ has incomplete type and cannot be defined

Demo

iammilind
  • 68,093
  • 33
  • 169
  • 336
  • You are right - I'm looking for a more friendly way to warn users of class `Foo` - interesting implementation, with nice error msgs! – kfmfe04 Dec 18 '11 at 10:19