1

Hello and sorry for my bad English.

For the purpose to practice with c++11, I'm trying to write a version of the class std::experimental::any (http://en.cppreference.com/w/cpp/experimental/any) adding something extra.

Adding the operator<() I got a different behavior between g++ (4.9.2) and clang++ (3.5.0).

The following is a shortened version of the class (and classes that uses), covering the minimum necessary, and a very small main() to trigger the problem.

Sorry for the long code but I failed to make the example shorter.

#include <memory>
#include <iostream>
#include <type_traits>
#include <unordered_set>

namespace yans // yet another name space
 {
   class anyB  // base for any
    {
      public:

         virtual std::type_info const & typeT () const = 0;
         virtual bool isLess (anyB const *) const = 0;
    };

   template <typename T>
      class anyD : public anyB  // derived for any
       {
         private:

            T val;

            static std::type_info const & typeInfo ()
             { static auto const & ret = typeid(T); return ret; }

            template <typename U> // preferred version
               static auto lessF (U const & u1, U const & u2, int)
               -> decltype(  std::declval<U const &>()
                           < std::declval<U const &>())
                { return (u1 < u2); }

            template <typename U> // emergency version
               static auto lessF (U const &, U const &, ...) -> bool
                { throw std::runtime_error("no operator < for type "); }

         public:

            anyD (T const & v0)
               : val(v0)
                { }

            std::type_info const & typeT () const override final
             { return typeInfo(); }

            bool isLess (anyB const * pB0) const override final
             {
               auto pD0 = dynamic_cast<anyD<T> const *>(pB0);

               if ( nullptr == pD0 )
                  throw std::bad_cast();

               return lessF(val, pD0->val, 0);
             }
       };

   class any
    {
      private:
         template <class T>
            using sT = typename std::decay<T>::type;

         template <class T>
            using noAny
            = typename std::enable_if
            <false == std::is_same<any, sT<T>>::value, bool>::type;

         template <class T>
            using isCpCtr
            = typename std::enable_if
            <true == std::is_copy_constructible<sT<T>>::value,bool>::type;

         std::unique_ptr<anyB>  ptr;

         static std::type_info const & voidInfo ()
          { static auto const & ret = typeid(void); return ret; }

         bool opLess (any const & a0) const
          {
            return
                  type().before(a0.type())
               || (   (type() == a0.type())
                   && (false == empty())
                   && ptr.get()->isLess(a0.ptr.get()) );
          }

      public:

         template <typename T, typename = noAny<T>, typename = isCpCtr<T>>
            any (T && v0)
            : ptr(new anyD<sT<T>>(std::forward<T>(v0)))
             { }

         bool empty () const noexcept
          { return ! bool(ptr); }

         std::type_info const & type () const
          { return ( ptr ? ptr->typeT() : voidInfo()); }

         friend bool operator< (any const &, any const &);
    };

   bool operator< (any const & a0, any const & a1)
    { return a0.opLess(a1); }
 }

int main () 
 {
   try
    {
      yans::any  ai { 12 };
      yans::any  as { std::string("t1") };
      yans::any  au { std::unordered_set<int> { 1, 5, 3 } };

      std::cout << "ai < 13 ? " << (ai < 13) << '\n';
      std::cout << "as < std::string {\"t0\"} ? "
         << (as < std::string {"t0"}) << '\n';
      std::cout << "au < std::unordered_set<int> { 2, 3, 4 } ? "
         << (au < std::unordered_set<int> { 2, 3, 4 }) << '\n';
    }
   catch ( std::exception const & e )
    {
      std::cerr << "\nmain(): standard exception of type \""
         << typeid(e).name() <<"\"\n"
         << "  ---> " << e.what() << " <---\n\n";
    }

   return EXIT_SUCCESS;
 }

The idea, behind operator<(), is to return "true" if the type of the left operand is less than the type of the right (according typeid(T).before()) and, if the types match, to return the value returned by the comparison of contained values. I know it's a questionable solution, but I'm playing to learn.

The trouble is that in the instances of class any could be included values of types without operator<(). In the example, an instance of the class std::unordered_set<int>. Then I tried to develop a couple of overloaded (SFINAE) method lessF(); the preferred one, when operator<() is available for the type T included, return the comparison value; the emergency version, used when operator<() is unavailable, throw an exception.

With the clang++, I get what I wish: the class anyD<std::unordered_set<int>> does not implement the preferred version of lessF and comparison generates an exception from the emergency version.

With the g++, on the contrary, the class anyD<std::unordered_set<int>> generates the preferential version invoking the operator<() on two instances of any, build on the two std::unordered_set<int> arguments of lessF() (the templated constructor of any isn't "explicit" defined), and, then, recursively invokes itself, going in loop and generating an error ("Errore di segmentazione", that is "segmentation fault").

What I'd like to understand is:

  • According to the ISO c++11, it's correct the behavior of clang++ or the behaviour of g++?

  • Can I prevent locally (only in lessF()), and without declaring "explicit" the template constructor of any(), that the comparison between two std::unordered_set<int> will turn itself in the confrontation between two any? In other words: how can I prevent the preferential version of lessF() to be developed in anyD<std::unordered_set<int>>?

The following are the outputs of the two programs.

---- clang++ program output ----
ai < 13 ? 1
as < std::string {"t0"} ? 0
au < std::unordered_set<int> { 2, 3, 4 } ? 
main(): standard exception of type "St13runtime_error"
  ---> no operator < for type  <---

---- end output ----

---- g++ program output ----
ai < 13 ? 1
as < std::string {"t0"} ? 0
Errore di segmentazione
---- end output ----

1 Answer 1

3

I believe you have uncovered a bug in gcc (I have reproduced it in much shorter form here, stay tuned).

The problem is, if you look through the segmentation fault, you'll see that the operator< call for unordered_set<int> is infinitely recursive. This is because gcc actually considers bool operator<(const any&, const any&) to be a match. It shouldn't be at the point where you're calling it.

The easy fix is to simply ensure that operator<(const any&, const any&) is only found for any's, regardless of what namespace you're in. Simply move the definition into the class:

class any {
    friend bool operator< (any const & a0, any const & a1) {
        return a0.opLess(a1);
    }
};

This is good practice anyway.

Sign up to request clarification or add additional context in comments.

4 Comments

@ Barry, I think you're right: it is a visibility problem. I put the following lines before the virtual class definition (anyB) class any; bool operator< (any const & a0, any const & a1); and now also the program generated by clang++ goes in a loop and generates segmentation fault. And congratulations for your example: extremely simple.
@ Barry, but about your solution ... well ... in your way I lose the ability to compare an instance of any class with a generic type T; as in the example program. I think it would be appropriate to put the explicit template constructor (and develop the template specializations for the operator when an any object is confronted with a generic T object). However, in the specifications for std::experiment::any, the template constructor isn't explicit.
@max66 If you do what I suggest, you'd still be able to compare an any with a non-any. ADL would find the right operator < and perform the conversion.
@ Batty: you're absolutely right . I get an error, but I made a very silly mistake. Thanks Barry.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.