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 ----