Opened 5 years ago

Closed 5 years ago

Last modified 5 years ago

#13012 closed Bugs (fixed)

boost::has_equal_to fails with captureless lambda expression types

Reported by: Joaquín M López Muñoz Owned by: John Maddock
Milestone: To Be Determined Component: type_traits
Version: Boost 1.63.0 Severity: Problem
Keywords: Cc:

Description

The following

include <boost/type_traits/has_equal_to.hpp>
 
int main()
{
  auto f=[](){};
  (void)boost::has_equal_to<decltype(f)>::value;
}

generates a compile time error about an ambiguity for operator==, see http://coliru.stacked-crooked.com/a/f1010ec991d2721a. The problem has been tested to show in GCC and VS2015. For what it's worth, capturing lambda expressions do not trigger the problem.

Change History (17)

comment:1 by John Maddock, 5 years ago

Resolution: fixed
Status: newclosed

This is a somewhat documented issue, the example in the docs is:

struct A { };
void operator==(const A&, const A&);
struct B { operator A(); };
boost::has_equal_to<A>::value; // this is fine
boost::has_equal_to<B>::value; // error: ambiguous overload

A similar situation occurs with a captureless lambda, because f in your example above is really a class that looks like:

struct some_obscure_name
{
   operator void (*)()()const;
};

However, it turns out that in C++11 we can do better, for modern compilers this is fixed in the sequence of commits in: https://github.com/boostorg/type_traits/pull/64

Note that I still have to update the docs.

Also note that whether a lambda is comparable to other lambdas is compiler specific: it is for gcc (via unambiguous conversion to function pointer), but not for msvc which has implicit conversions to 2 different function pointer types and is therefore ambiguous. So the new code reports true for gcc and false for msvc.

comment:2 by Joaquín M López Muñoz, 5 years ago

Hi John, thanks for fixing this.

Unfortunately, in my use case I'm supporting GCC 4.8, which your fix is not. You might want to consider how I'm handling stateless lambdas in non-expression SFINAE scenarios via boost::poly_collection::detail::is_likely_stateless_lambda at:

https://github.com/boostorg/poly_collection/blob/develop/include/boost/poly_collection/detail/is_equality_comparable.hpp

Best,

Last edited 5 years ago by Joaquín M López Muñoz (previous) (diff)

comment:3 by John Maddock, 5 years ago

I confess I'm not that keen on jumping through hoops for gcc-4.8, however this patch: https://github.com/boostorg/type_traits/commit/28658d6c11228ead4f284f9aaaf0b129a64a3e35 half-fixes the issue - the code will compile, but will return false for all lambdas.

comment:4 by Joaquín M López Muñoz, 5 years ago

Does this work if the signature of the stateless lambda expression is not void()?

comment:5 by John Maddock, 5 years ago

No, but I think that signature is mandated by the standard? Or are lambdas with function arguments also convertible to function pointers?

comment:6 by Joaquín M López Muñoz, 5 years ago

I'm afraid stateless lambda expressions of any signature convert to the corresponding function pointer.

comment:7 by John Maddock, 5 years ago

Nod. I realised that right after I went to bed!

I've pulled your is_likely_stateless_lambda into type traits as a workaround for this case - and it sort of mostly works for gcc-4.7 and 4.8. However, gcc-4.6 and earlier does not handle the code, neither do any of the msvc versions effected (12 and earlier). So it's not much of fix to be honest.

comment:8 by anonymous, 5 years ago

Hi John,

This is terrific, thank you. I'm removing my own workaround then and relying on Boost.TypeTraits unconditionally, will report if something arises.

  • Do you plan to merge to release in time for 1.67? Just asking to keep Boost.PolyCollection in sync.
  • Two nitpicks:

Thanks again

comment:9 by Joaquín M López Muñoz, 5 years ago

Hi,

I see that boost::type_traits_detail::is_likely_stateless_lambda defaults out for MSVC at

https://github.com/boostorg/type_traits/blob/5240cd67d895dd811ca708eb5faa835de73f25b8/include/boost/type_traits/detail/is_likely_lambda.hpp#L30

but my local tests seem to show the lambda detection machinery indeed works for MSVC 14.0 --don't know about 12.0. Would it be possible to relax the aforementioned line so that MSVC >=14.0 is accepted?

Thank you

comment:10 by John Maddock, 5 years ago

MSVC-14 uses the new "accurate" detection code and doesn't need/use the workaround. msvc-12 does need the workaround but can't compile it.

Meanwhile I've just merged to master.

comment:11 by anonymous, 5 years ago

Umm, Ive run the following in Visual Studio 2015 against develop

#include <boost/type_traits/has_equal_to.hpp>
#include <iostream>

int main()
{
  auto f=[](int){return 0;};
  std::cout<<boost::has_equal_to<decltype(f),decltype(f),bool>::value<<"\n";
}

and compiler fails with:

1>------ Build started: Project: sandbox, Configuration: Debug Win32 ------
1>  sandbox.cpp
1>d:\usr\joaquin\proyectos\boost-repo\trunk\boost\type_traits\detail\has_binary_operator.hpp(212): error C2593: 'operator ==' is ambiguous
1>  d:\usr\joaquin\proyectos\boost-repo\trunk\boost\type_traits\detail\has_binary_operator.hpp(124): note: could be 'boost::detail::has_equal_to_impl::no_operator boost::detail::has_equal_to_impl::operator ==(const boost::detail::has_equal_to_impl::any &,const boost::detail::has_equal_to_impl::any &)'
1>  d:\usr\joaquin\proyectos\boost-repo\trunk\boost\type_traits\detail\has_binary_operator.hpp(212): note: while trying to match the argument list '(main::<lambda_7042359f6847725571dfbc3d7e478577>, main::<lambda_7042359f6847725571dfbc3d7e478577>)'
1>  d:\usr\joaquin\proyectos\boost-repo\trunk\boost\type_traits\detail\has_binary_operator.hpp(233): note: see reference to class template instantiation 'boost::detail::has_equal_to_impl::operator_exists<Lhs,Rhs>' being compiled
1>          with
1>          [
1>              Lhs=main::<lambda_7042359f6847725571dfbc3d7e478577>,
1>              Rhs=main::<lambda_7042359f6847725571dfbc3d7e478577>
1>          ]
1>  d:\usr\joaquin\proyectos\boost-repo\trunk\boost\type_traits\detail\has_binary_operator.hpp(262): note: see reference to class template instantiation 'boost::detail::has_equal_to_impl::trait_impl1<main::<lambda_7042359f6847725571dfbc3d7e478577>,main::<lambda_7042359f6847725571dfbc3d7e478577>,Ret,false>' being compiled
1>          with
1>          [
1>              Ret=bool
1>          ]
1>  d:\usr\joaquin\proyectos\boost-repo\trunk\boost\type_traits\detail\has_binary_operator.hpp(270): note: see reference to class template instantiation 'boost::detail::has_equal_to_impl::trait_impl<Lhs,Rhs,Ret>' being compiled
1>          with
1>          [
1>              Lhs=main::<lambda_7042359f6847725571dfbc3d7e478577>,
1>              Rhs=main::<lambda_7042359f6847725571dfbc3d7e478577>,
1>              Ret=bool
1>          ]
1>  d:\usr\joaquin\proyectos\poly_collection\sandbox\sandbox.cpp(9): note: see reference to class template instantiation 'boost::has_equal_to<main::<lambda_7042359f6847725571dfbc3d7e478577>,main::<lambda_7042359f6847725571dfbc3d7e478577>,bool>' being compiled
1>d:\usr\joaquin\proyectos\boost-repo\trunk\boost\type_traits\detail\has_binary_operator.hpp(149): error C2593: 'operator ==' is ambiguous
1>  d:\usr\joaquin\proyectos\boost-repo\trunk\boost\type_traits\detail\has_binary_operator.hpp(124): note: could be 'boost::detail::has_equal_to_impl::no_operator boost::detail::has_equal_to_impl::operator ==(const boost::detail::has_equal_to_impl::any &,const boost::detail::has_equal_to_impl::any &)'
1>  d:\usr\joaquin\proyectos\boost-repo\trunk\boost\type_traits\detail\has_binary_operator.hpp(149): note: while trying to match the argument list '(main::<lambda_7042359f6847725571dfbc3d7e478577>, main::<lambda_7042359f6847725571dfbc3d7e478577>)'
1>  d:\usr\joaquin\proyectos\boost-repo\trunk\boost\type_traits\detail\has_binary_operator.hpp(233): note: see reference to class template instantiation 'boost::detail::has_equal_to_impl::operator_returns_void<Lhs,Rhs>' being compiled
1>          with
1>          [
1>              Lhs=main::<lambda_7042359f6847725571dfbc3d7e478577>,
1>              Rhs=main::<lambda_7042359f6847725571dfbc3d7e478577>
1>          ]
1>d:\usr\joaquin\proyectos\boost-repo\trunk\boost\type_traits\detail\has_binary_operator.hpp(194): error C2593: 'operator ==' is ambiguous
1>  d:\usr\joaquin\proyectos\boost-repo\trunk\boost\type_traits\detail\has_binary_operator.hpp(124): note: could be 'boost::detail::has_equal_to_impl::no_operator boost::detail::has_equal_to_impl::operator ==(const boost::detail::has_equal_to_impl::any &,const boost::detail::has_equal_to_impl::any &)'
1>  d:\usr\joaquin\proyectos\boost-repo\trunk\boost\type_traits\detail\has_binary_operator.hpp(194): note: while trying to match the argument list '(main::<lambda_7042359f6847725571dfbc3d7e478577>, main::<lambda_7042359f6847725571dfbc3d7e478577>)'
1>  d:\usr\joaquin\proyectos\boost-repo\trunk\boost\type_traits\detail\has_binary_operator.hpp(233): note: see reference to class template instantiation 'boost::detail::has_equal_to_impl::operator_returns_Ret<Lhs,Rhs,Ret,false>' being compiled
1>          with
1>          [
1>              Lhs=main::<lambda_7042359f6847725571dfbc3d7e478577>,
1>              Rhs=main::<lambda_7042359f6847725571dfbc3d7e478577>,
1>              Ret=bool
1>          ]

comment:12 by John Maddock, 5 years ago

Works just fine for me, I'm guessing you have an obsolete VC2015 install (not updated), can you add:

#ifdef BOOST_NO_SFINAE_EXPR
#pragma message("BOOST_NO_SFINAE_EXPR is set")
#endif
#ifdef BOOST_NO_CXX11_DECLTYPE
#pragma message("BOOST_NO_CXX11_DECLTYPE is set")
#endif
#ifdef BOOST_TT_HAS_ACCURATE_BINARY_OPERATOR_DETECTION
#pragma message("BOOST_TT_HAS_ACCURATE_BINARY_OPERATOR_DETECTION is set")
#endif
#pragma message(BOOST_STRINGIZE(_MSC_FULL_VER))

To your test case? I get:

BOOST_TT_HAS_ACCURATE_BINARY_OPERATOR_DETECTION is set
190024215

comment:13 by anonymous, 5 years ago

Here I get:

BOOST_NO_SFINAE_EXPR is set
190023026

comment:14 by John Maddock, 5 years ago

OK that's a non-updated VS2015 install. Note that at the current time there is no way to download that version (you automatically get update 3), nor do we have any testers or CI for that version, so the official answer for that issue would always be "please update your visual studio install with the latest patches".

However, if you can run some tests for me it would do no harm to add patches for that version, please just be aware that they will never be tested, so eventual breakage is not entirely unlikely.

Can you cd into libs/type_traits/test and run:

../../../b2 msvc-14.0 define=CI_SUPPRESS_KNOWN_ISSUES define=BOOST_TT_HAS_ACCURATE_BINARY_OPERATOR_DETECTION

I'll assume that will generate errors, if so then inside is_likely_lambda.hpp change:

#elif !defined(BOOST_NO_CXX11_LAMBDAS) && !defined(BOOST_NO_CXX11_DECLTYPE) && !defined(BOOST_NO_CXX11_TEMPLATE_ALIASES) && !defined(BOOST_MSVC)

to

#elif !defined(BOOST_NO_CXX11_LAMBDAS) && !defined(BOOST_NO_CXX11_DECLTYPE) && !defined(BOOST_NO_CXX11_TEMPLATE_ALIASES) && !BOOST_WORKAROUND(BOOST_MSVC, < 1900)

and run the tests again with:

../../../b2 msvc-14.0 define=CI_SUPPRESS_KNOWN_ISSUES

and report back?

Thanks!

comment:15 by anonymous, 5 years ago

Reporting back:

  • First b2 run generated a lot of errors as you predicted. Please let me know if you want me to send you a logzip.
  • Did change the line in is_likely_lambda.hpp as instructed.
  • Second b2 run failed two tests as follows:
>b2 msvc-14.0 define=CI_SUPPRESS_KNOWN_ISSUES
Performing configuration checks

    - 32-bit                   : yes (cached)
    - arm                      : no  (cached)
    - mips1                    : no  (cached)
    - power                    : no  (cached)
    - sparc                    : no  (cached)
    - x86                      : yes (cached)
    - symlinks supported       : no  (cached)
    - junctions supported      : yes (cached)
    - hardlinks supported      : yes (cached)
...patience...
...found 3507 targets...
...updating 8 targets...
testing.capture-output ..\..\..\bin.v2\libs\type_traits\test\has_nothrow_destruc
tor_test.test\msvc-14.0\debug\threadapi-win32\threading-multi\has_nothrow_destru
ctor_test.run
====== BEGIN OUTPUT ======
has_nothrow_destructor_test.cpp:194: <note>The expression: "::boost::has_nothrow
_destructor<UDT>::value" did not have the value we wish it to have (found 0, exp
ected 1)</note>
has_nothrow_destructor_test.cpp:195: <note>The expression: "::boost::has_nothrow
_destructor<empty_UDT>::value" did not have the value we wish it to have (found
0, expected 1)</note>
has_nothrow_destructor_test.cpp:203: <note>The expression: "::boost::has_nothrow
_destructor<trivial_except_destroy>::value" did not have the value we wish it to
 have (found 0, expected 1)</note>
has_nothrow_destructor_test.cpp:207: <note>The expression: "::boost::has_nothrow
_destructor<wrap<trivial_except_destroy> >::value" did not have the value we wis
h it to have (found 0, expected 1)</note>
has_nothrow_destructor_test.cpp:214: The expression: "::boost::has_nothrow_destr
uctor<noexcept_destruct>::value" had an invalid value (found 0, expected 1)
has_nothrow_destructor_test.cpp:215: The expression: "::boost::has_nothrow_destr
uctor<nothrow_destruct>::value" had an invalid value (found 0, expected 1)

EXIT STATUS: 2
====== END OUTPUT ======


    set status=0
    if %status% NEQ 0 (
        echo Skipping test execution due to testing.execute=off
        exit 0
    )
     "..\..\..\bin.v2\libs\type_traits\test\has_nothrow_destructor_test.test\msv
c-14.0\debug\threadapi-win32\threading-multi\has_nothrow_destructor_test.exe"
> "..\..\..\bin.v2\libs\type_traits\test\has_nothrow_destructor_test.test\msvc-1
4.0\debug\threadapi-win32\threading-multi\has_nothrow_destructor_test.output" 2>
&1
    set status=%ERRORLEVEL%
    echo. >> "..\..\..\bin.v2\libs\type_traits\test\has_nothrow_destructor_test.
test\msvc-14.0\debug\threadapi-win32\threading-multi\has_nothrow_destructor_test
.output"
    echo EXIT STATUS: %status% >> "..\..\..\bin.v2\libs\type_traits\test\has_not
hrow_destructor_test.test\msvc-14.0\debug\threadapi-win32\threading-multi\has_no
throw_destructor_test.output"
    if %status% EQU 0 (
        copy "..\..\..\bin.v2\libs\type_traits\test\has_nothrow_destructor_test.
test\msvc-14.0\debug\threadapi-win32\threading-multi\has_nothrow_destructor_test
.output" "..\..\..\bin.v2\libs\type_traits\test\has_nothrow_destructor_test.test
\msvc-14.0\debug\threadapi-win32\threading-multi\has_nothrow_destructor_test.run
"
    )
    set verbose=0
    if %status% NEQ 0 (
        set verbose=1
    )
    if %verbose% EQU 1 (
        echo ====== BEGIN OUTPUT ======
        type "..\..\..\bin.v2\libs\type_traits\test\has_nothrow_destructor_test.
test\msvc-14.0\debug\threadapi-win32\threading-multi\has_nothrow_destructor_test
.output"
        echo ====== END OUTPUT ======
    )
    exit %status%

...failed testing.capture-output ..\..\..\bin.v2\libs\type_traits\test\has_nothr
ow_destructor_test.test\msvc-14.0\debug\threadapi-win32\threading-multi\has_noth
row_destructor_test.run...
testing.capture-output ..\..\..\bin.v2\libs\type_traits\test\is_default_constr_t
est.test\msvc-14.0\debug\threadapi-win32\threading-multi\is_default_constr_test.
run
====== BEGIN OUTPUT ======
is_default_constr_test.cpp:200: The expression: "(::boost::is_default_constructi
ble<std::pair<deleted_default_construct, int> >::value)" had an invalid value (f
ound 1, expected 0)
is_default_constr_test.cpp:204: The expression: "(::boost::is_default_constructi
ble<std::pair<private_default_construct, int> >::value)" had an invalid value (f
ound 1, expected 0)

EXIT STATUS: 2
====== END OUTPUT ======
failed to write output file '..\..\..\bin.v2\libs\type_traits\test\is_nothrow_mo
ve_constructible_test_no_intrinsics.test\msvc-14.0\debug\threadapi-win32\threadi
ng-multi\is_nothrow_move_constructible_test_no_intrinsics.exe.rsp'!


    set status=0
    if %status% NEQ 0 (
        echo Skipping test execution due to testing.execute=off
        exit 0
    )
     "..\..\..\bin.v2\libs\type_traits\test\is_default_constr_test.test\msvc-14.
0\debug\threadapi-win32\threading-multi\is_default_constr_test.exe"   > "..\..\.
.\bin.v2\libs\type_traits\test\is_default_constr_test.test\msvc-14.0\debug\threa
dapi-win32\threading-multi\is_default_constr_test.output" 2>&1
    set status=%ERRORLEVEL%
    echo. >> "..\..\..\bin.v2\libs\type_traits\test\is_default_constr_test.test\
msvc-14.0\debug\threadapi-win32\threading-multi\is_default_constr_test.output"
    echo EXIT STATUS: %status% >> "..\..\..\bin.v2\libs\type_traits\test\is_defa
ult_constr_test.test\msvc-14.0\debug\threadapi-win32\threading-multi\is_default_
constr_test.output"
    if %status% EQU 0 (
        copy "..\..\..\bin.v2\libs\type_traits\test\is_default_constr_test.test\
msvc-14.0\debug\threadapi-win32\threading-multi\is_default_constr_test.output" "
..\..\..\bin.v2\libs\type_traits\test\is_default_constr_test.test\msvc-14.0\debu
g\threadapi-win32\threading-multi\is_default_constr_test.run"
    )
    set verbose=0
    if %status% NEQ 0 (
        set verbose=1
    )
    if %verbose% EQU 1 (
        echo ====== BEGIN OUTPUT ======
        type "..\..\..\bin.v2\libs\type_traits\test\is_default_constr_test.test\
msvc-14.0\debug\threadapi-win32\threading-multi\is_default_constr_test.output"
        echo ====== END OUTPUT ======
    )
    exit %status%

...failed testing.capture-output ..\..\..\bin.v2\libs\type_traits\test\is_defaul
t_constr_test.test\msvc-14.0\debug\threadapi-win32\threading-multi\is_default_co
nstr_test.run...

comment:16 by anonymous, 5 years ago

Thanks for that, pushed to develop, once CI has completed I'll merge to master.

comment:17 by anonymous, 5 years ago

Thank you, happily using your improved has_equal_to, waiting for test runners to cycle before merging to master (hopefully in time for Boost 1.67).

Note: See TracTickets for help on using tickets.