Search code examples
c++c++17std-variant

Unable to visit a variant if one of its alternatives does not have a specific field


I am trying to visit a variant containing several classes. One of them does not have a specific field value but I handle it with constexpr, however, the compiler still fails to compile.

#include <variant>
#include <iostream>

struct A {};

struct B {
    int value = 1;
};

struct C {
    int value = 2;
};

int main() {

    const auto d = std::variant<A, B, C>{B{}};
    auto n = std::visit(
        [&](auto &data) -> int {
            if constexpr (std::is_same_v<decltype(data), A>) {
                return int{0};
            } else {
                return data.value;
            }
        },
        d);
    std::cout << n << std::endl;
    return 0;
}

Error:

error: ‘const struct A’ has no member named ‘value’
   22 |                 return data.value;

Compiler version:

clang --version
clang version 10.0.0-4ubuntu1
Target: x86_64-pc-linux-gnu
Thread model: posix
InstalledDir: /usr/bin

Do I miss something or it is impossible by design?

EDIT: The shortest solution was to use std::decay_t, however, I don't know if there can be any consequences:

if constexpr (std::is_same_v<std::decay_t<decltype(data)>, A>)

Solution

  • You need remove reference and cv-qualifiers of the data:

    auto n = std::visit(
            [&](auto &data) -> int {
                if constexpr (std::is_same_v<std::remove_cvref_t<decltype(data)>, A>) {
                    return int{0};
                } else {
                    return data.value;
                }
            },
            d);
    

    Demo

    In C++20, you can just use the requires clause to detect whether the data.value expression is valid.

    const auto d = std::variant<A, B, C>{B{}};
        auto n = std::visit(
            [&](auto& data) -> int {
                if constexpr (requires { data.value; }) {
                    return data.value;
                } else {
                    return int{0};
                }
            },
            d);