Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Visit + Lambda #44

Closed
erakis opened this issue Mar 28, 2018 · 9 comments
Closed

Visit + Lambda #44

erakis opened this issue Mar 28, 2018 · 9 comments

Comments

@erakis
Copy link

erakis commented Mar 28, 2018

Hi,

I'm trying to use lambda instead of functor to visit a variant element. But is it possible to get compilation error when lambda do not handle all variant type ? Same behavior as a functor ?

Ex :

typedef mpark::variant<
    int8_t, uint8_t,
    double,
    std::string
> VariantValue;

VariantValue v{ (double)2.0 };

mpark::visit([](auto&& arg)
{
    using T = std::decay_t<decltype(arg)>;
    if (std::is_same_v<T, int8_t>)
        std::cout << "int8_t with value " << arg << '\n';
    else if (std::is_same_v<T, std::string>)
        std::cout << "std::string with value " << arg << '\n';
    else
        // static_assert(always_false<T>::value, "non-exhaustive visitor!");
        // static_assert(mpark::lib::is_invocable<T, arg>::value, "non-exhaustive visitor!");

        // ----------------------------------------------------------------------
        // I tried many thing but I'm always getting compilation error or
        // I can't achieve the behavior I'm look for.
}, v);

Important : I'm using Visual Studio 2015 Update 3 and GCC 4.9.1

Ref: http://en.cppreference.com/w/cpp/utility/variant/visit

@mpark
Copy link
Owner

mpark commented Mar 28, 2018

In order for this technique to work, you need if constexpr, which you don't have prior to C++17.

@erakis
Copy link
Author

erakis commented Mar 29, 2018

@mpark , thank you for you time.

So without VS 2017 I'm stuck using functor ? But If I use lambda, it will be WITHOUT types validation at compile time ?

@SaadAttieh
Copy link

SaadAttieh commented Mar 29, 2018 via email

@erakis
Copy link
Author

erakis commented Mar 29, 2018

@SaadAttieh thank you.

I tried with

template <class... Fs>
struct OverLoaded;

template <class F1>
struct OverLoaded<F1> : F1 {
    using F1::operator();

    OverLoaded(F1&& head) : F1(std::forward<F1>(head)) {}
};

template <class F1, class... Fs>
struct OverLoaded<F1, Fs...> : F1, OverLoaded<Fs...> {
    using F1::operator();
    using OverLoaded<Fs...>::operator();

    OverLoaded(F1&& head, Fs&&... rest)
        : F1(std::forward<F1>(head)),
        OverLoaded<Fs...>(std::forward<Fs>(rest)...) {}
};

template <class... Fs>
OverLoaded<Fs...> overloaded(Fs&&... fs) {
    return OverLoaded<Fs...>(std::forward<Fs>(fs)...);
}
typedef mpark::variant<
    int8_t, uint8_t,
    double,
    std::string
> VariantValue;

VariantValue v((double)2.0);

Doing this compile fine and work great.

mpark::visit(overloaded(
    [](int a) { std::cout << a / 2 << std::endl; },
    [](std::string& a) { std::cout << a + "\n" << std::endl; }//,
    [](auto&) { std::cout << "Catch all\n" << std::endl; }
), v);

But this compile fine too but I'm getting a warning instead of a compilation error.

mpark::visit(overloaded(
    [](int a) { std::cout << a / 2 << std::endl; },
    [](std::string& a) { std::cout << a + "\n" << std::endl; }//,
    //[](auto&) { std::cout << "Catch all\n" << std::endl; }
), v);

The warning

1>d:\xxx\include\mpark\lib.hpp(237): warning C4244: 'argument': conversion from 'double' to 'int', possible loss of data
1>  d:\xxx\include\mpark\variant.hpp(684): note: see reference to function template instantiation 'void mpark::lib::cpp17::invoke<_Ty,T&>(F &&,T &)' being compiled
1>          with
1>          [
1>              _Ty=OverLoaded<main::<lambda_31e307beed92b69987871195c711937f>,main::<lambda_d72897b211ce01db93a67d525626e679>>,
1>              T=double,
1>              F=OverLoaded<main::<lambda_31e307beed92b69987871195c711937f>,main::<lambda_d72897b211ce01db93a67d525626e679>>
1>          ]
...

@SaadAttieh
Copy link

SaadAttieh commented Mar 30, 2018 via email

@SaadAttieh
Copy link

SaadAttieh commented Mar 30, 2018 via email

@erakis
Copy link
Author

erakis commented Mar 30, 2018

Initially the variant I'm using it's this one

typedef mpark::variant<
    bool,
    int8_t, uint8_t,
    int16_t, uint16_t,
    int32_t, uint32_t,
    int64_t, uint64_t,
    float, double,
    std::string
> VariantValue;

I ask my question without putting all types in the variant because I though the 3 initial one was enough to expose my problem.

If I'm using reference instead of value on Visual Studio 2015 Update 3, I'm still not getting compilation error when commenting the catch all instruction.

VariantValue v((double)2.0);

mpark::visit(overloaded(
    [](const uint8_t& a) { std::cout << a / 2 << std::endl; },
    [](const std::string& a) { std::cout << a + "\n" << std::endl; }//,
    //[](auto&) { std::cout << "Catch all\n" << std::endl; }
), v);

// I'm still getting the same warning about 
// Warning C4244	'argument': conversion from 'double' to 'const uint8_t', possible loss of data

I think I will have to go with the functor and forget about lambda until I get compliant C++17 compiler. As I'm using an old version of Yocto and I cannot upgrade to a newer version easily.

@SaadAttieh
Copy link

SaadAttieh commented Mar 30, 2018 via email

@mpark
Copy link
Owner

mpark commented Mar 30, 2018

@SaadAttieh: Thanks for helping out with this!

@purell: @SaadAttieh's solution isn't working for you because you're taking const int&
rather than int&. const int& will take a double whereas int& would not. But this
solution only works if you're always visiting l-value variants, and you're forced to forgo
const correctness.

If you really want to handle the exact types without implicit conversions, you probably
want a different abstraction than visit. For example, you could introduce
a type_switch like this:

  mpark::variant<int, double> v = 1.1, w = 101;

  // single visitation  
  type_switch(v)(
    [](type<int>, auto&& x) { std::cout << "int: " << x << '\n'; },
    [](type<double>, auto&& x) { std::cout << "double: " << x << '\n'; });

  // double visitation
  type_switch(v, w)(
    [](type<int, int>, auto&& x, auto&& y) {
      std::cout << "int, int: " << x << ' ' << y << '\n';
    },
    [](type<double, int>, auto&& x, auto&& y) {
      std::cout << "double, int: " << x << ' ' << y << '\n';
    },
    [](type<int, double>, auto&& x, auto&& y) {
      std::cout << "int, double: " << x << ' ' << y << '\n';
    },
    [](type<double, double>, auto&& x, auto&& y) {
      std::cout << "double, double: " << x << ' ' << y << '\n';
    });

Given template <typename... Ts> struct type {}; and an implementation
of overload, type_switch can be implemented like this:

template <typename... Vs>
auto type_switch(Vs&&... vs) {  // take the variants
  return [&](auto&&... fs) {  // take the cases
    mpark::visit([&](auto&&... xs) {  // visit the variants, get the values
      return overload(std::forward<decltype(fs)>(fs)...)(  // overload the cases
        type<std::decay_t<decltype(xs)>...>{},  // perform tag-dispatching with `type`
        std::forward<decltype(xs)>(xs)...);  // forward the values
    }, std::forward<Vs>(vs)...);
  };
}

Example on Wandbox

@mpark mpark closed this as completed Jun 19, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants