vittorioromeo / scelta

(experimental) Syntactic sugar for variant and optional types.
https://vittorioromeo.info
MIT License
157 stars 11 forks source link

Generic lambdas and const-qualified mem funcs break `map` and `and_then` #10

Open TartanLlama opened 6 years ago

TartanLlama commented 6 years ago

Adding this code to optional_monadic.cpp:

struct foo {
    foo non_const() { return {}; }
};

//...
with_all_optional_implementations<foo>( 
        [](auto make) {
                const auto a = make();
                auto f = [](auto &&x) { return x.non_const(); };
                scelta::map(a, f);
         }
);

results in this compiler error on GCC 7.2:

/home/simon/scelta/test/optional/optional_monadic.cpp: In instantiation of ‘main()::<lambda(auto:16)>::<lambda(auto:17&&)> [with auto:17 = const foo&; auto:16 = test::impl::maker_t<std::optional<foo> >]’:
/home/simon/scelta/include/scelta/./utils/optional_monadic.hpp:30:5:   required by substitution of ‘template<class Optional, class T, class F> constexpr decltype ((scelta::is_nullopt(o) ? forward<decltype (def)>(def) : forward<decltype (f)>(f)(scelta::impl::access_optional(forward<decltype (o)>(o))))) scelta::map_or(Optional&&, T&&, F&&) [with Optional = const std::optional<foo>&; T = std::optional<foo>; F = main()::<lambda(auto:16)> [with auto:16 = test::impl::maker_t<std::optional<foo> >]::<lambda(auto:17&&)>&]’
/home/simon/scelta/include/scelta/./utils/optional_monadic.hpp:39:5:   required by substitution of ‘template<class Optional, class F> constexpr decltype (scelta::map_or(forward<decltype (o)>(o), std::decay_t<_Tp>{}, forward<decltype (f)>(f))) scelta::map(Optional&&, F&&) [with Optional = const std::optional<foo>&; F = main()::<lambda(auto:16)> [with auto:16 = test::impl::maker_t<std::optional<foo> >]::<lambda(auto:17&&)>&]’
/home/simon/scelta/test/optional/optional_monadic.cpp:139:28:   required from ‘main()::<lambda(auto:16)> [with auto:16 = test::impl::maker_t<std::optional<foo> >]’
/home/simon/scelta/test/optional/../variant_test_utils.hpp:117:28:   required from ‘test::with_all_optional_implementations(TF&&)::<lambda(auto:14)> [with auto:14 = test::impl::unpack_alternatives<test::impl::alternatives_t<foo> >::applier<std::optional>; TAlternative = foo; TF = main()::<lambda(auto:16)>]’
/home/simon/scelta/test/optional/../variant_test_utils.hpp:87:10:   required from ‘void test::instantiate_with_all_optional_implementations(TF&&) [with TestCase = test::impl::unpack_alternatives<test::impl::alternatives_t<foo> >::applier; TF = test::with_all_optional_implementations(TF&&) [with TAlternative = foo; TF = main()::<lambda(auto:16)>]::<lambda(auto:14)>]’
/home/simon/scelta/test/optional/../variant_test_utils.hpp:116:80:   required from ‘void test::with_all_optional_implementations(TF&&) [with TAlternative = foo; TF = main()::<lambda(auto:16)>]’
/home/simon/scelta/test/optional/optional_monadic.cpp:141:11:   required from here
/home/simon/scelta/test/optional/optional_monadic.cpp:138:63: error: passing ‘const foo’ as ‘this’ argument discards qualifiers [-fpermissive]
                 auto f = [](auto &&x) { return x.non_const(); };
                                                               ^
/home/simon/scelta/test/optional/optional_monadic.cpp:7:9: note:   in call to ‘foo foo::non_const()’
     foo non_const() { return {}; }
         ^~~~~~~~~
/home/simon/scelta/test/optional/optional_monadic.cpp: In instantiation of ‘main()::<lambda(auto:16)>::<lambda(auto:17&&)> [with auto:17 = const foo&; auto:16 = test::impl::maker_t<boost::optional<foo> >]’:
/home/simon/scelta/include/scelta/./utils/optional_monadic.hpp:30:5:   required by substitution of ‘template<class Optional, class T, class F> constexpr decltype ((scelta::is_nullopt(o) ? forward<decltype (def)>(def) : forward<decltype (f)>(f)(scelta::impl::access_optional(forward<decltype (o)>(o))))) scelta::map_or(Optional&&, T&&, F&&) [with Optional = const boost::optional<foo>&; T = boost::optional<foo>; F = main()::<lambda(auto:16)> [with auto:16 = test::impl::maker_t<boost::optional<foo> >]::<lambda(auto:17&&)>&]’
/home/simon/scelta/include/scelta/./utils/optional_monadic.hpp:39:5:   required by substitution of ‘template<class Optional, class F> constexpr decltype (scelta::map_or(forward<decltype (o)>(o), std::decay_t<_Tp>{}, forward<decltype (f)>(f))) scelta::map(Optional&&, F&&) [with Optional = const boost::optional<foo>&; F = main()::<lambda(auto:16)> [with auto:16 = test::impl::maker_t<boost::optional<foo> >]::<lambda(auto:17&&)>&]’
/home/simon/scelta/test/optional/optional_monadic.cpp:139:28:   required from ‘main()::<lambda(auto:16)> [with auto:16 = test::impl::maker_t<boost::optional<foo> >]’
/home/simon/scelta/test/optional/../variant_test_utils.hpp:117:28:   required from ‘test::with_all_optional_implementations(TF&&)::<lambda(auto:14)> [with auto:14 = test::impl::unpack_alternatives<test::impl::alternatives_t<foo> >::applier<boost::optional>; TAlternative = foo; TF = main()::<lambda(auto:16)>]’
/home/simon/scelta/test/optional/../variant_test_utils.hpp:91:10:   required from ‘void test::instantiate_with_all_optional_implementations(TF&&) [with TestCase = test::impl::unpack_alternatives<test::impl::alternatives_t<foo> >::applier; TF = test::with_all_optional_implementations(TF&&) [with TAlternative = foo; TF = main()::<lambda(auto:16)>]::<lambda(auto:14)>]’
/home/simon/scelta/test/optional/../variant_test_utils.hpp:116:80:   required from ‘void test::with_all_optional_implementations(TF&&) [with TAlternative = foo; TF = main()::<lambda(auto:16)>]’
/home/simon/scelta/test/optional/optional_monadic.cpp:141:11:   required from here
/home/simon/scelta/test/optional/optional_monadic.cpp:138:63: error: passing ‘const foo’ as ‘this’ argument discards qualifiers [-fpermissive]
                 auto f = [](auto &&x) { return x.non_const(); };
                                                               ^
/home/simon/scelta/test/optional/optional_monadic.cpp:7:9: note:   in call to ‘foo foo::non_const()’
     foo non_const() { return {}; }
         ^~~~~~~~~
make[3]: *** [test/CMakeFiles/test.optional.optional_monadic.dir/build.make:63: test/CMakeFiles/test.optional.optional_monadic.dir/optional/optional_monadic.cpp.o] Error 1
make[2]: *** [CMakeFiles/Makefile2:339: test/CMakeFiles/test.optional.optional_monadic.dir/all] Error 2
make[1]: *** [CMakeFiles/Makefile2:75: CMakeFiles/check.dir/rule] Error 2
make: *** [Makefile:175: check] Error 2

Expected behaviour: compile and run

This is because the implementation of map uses trailing return types, even in C++14 mode, which will cause the body of the lambda to be instantiated for the const-qualified overload, giving a hard error (it won't SFINAE). and_then has a similar issue. This could be solved by using auto return type deduction when compiling in C++14 mode.

It took me ages to work out why this broke for my implementation :laughing:

vittorioromeo commented 6 years ago

Thanks for the report, I will look into this. Using automatic return type deduction would prevent SFINAE-friendliness... I wonder if there's a way of solving this without resorting to that.

TartanLlama commented 6 years ago

I think it's a something of a fundamental issue with making SFINAE friendly wrappers for non-SFINAE friendly callables :disappointed:

You can fob it off on the user and make them give their lambdas return types, but it's not a great solution.

See http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0826r0.html for some discussion.

I didn't find a better solution then just having separate versions for C++14 and 11, but if you do I'd love to hear it so that I can steal it for my impl :laughing: