Use sizeof with incomplete type inside std::conditional
Asked Answered
I

5

5

Here is a minimal example:

struct incomplete_type;

template<typename T>
struct foo
{
    using type = std::conditional_t<std::is_arithmetic_v<T>,
        std::conditional_t<sizeof(T) < sizeof(void*), int, float>,
        double>;
};

foo<incomplete_type> f; will cause error because it will do sizeof with type, even though incomplete_type is not a arithmetic type(iow, it will not go into sizeof branch logically). live demo

So, I want to defer sizeof:

first attempt(fail)

template<typename T>
auto
foo_aux()
{
    if(sizeof(T) < sizeof(T*))
        return 0;
    else
        return 0.0f;
}

conditional_t<std::is_arithmetic_v<T>, decltype(foo_aux<T>()), double> still trigger the same error.

second attempt(fail)

template<typename T, bool>
struct foo_aux_aux
{
    using type = float;
};
template<typename T>
struct foo_aux_aux<T, true>
{
    using type = int;
};

template<typename T, bool = false>
struct foo_aux : foo_aux_aux<T, sizeof(T) < sizeof(void*)>
{};

conditional_t<std::is_arithmetic_v<T>, typename foo_aux<T>::type, double> still trigger the same error.

third attempt(success)

template<typename T, bool comp>
struct foo_aux_aux
{
    using type = float;
};
template<typename T>
struct foo_aux_aux<T, true>
{
    using type = int;
};

template<typename T, bool isArithmeticType>
struct foo_aux
{
    using type = double;
};

template<typename T>
struct foo_aux<T, true>
{
    using type = typename foo_aux_aux<T, sizeof(T) < sizeof(void*)>::type;
};

Yes, it works as expected, but its really tedious and ugly.

Do you have an elegant way here?

Iterate answered 1/12, 2018 at 9:12 Comment(1)
eel.is/c++draft/temp.pointLazy
L
6

In C++17, you can use if constexpr to do type computation. Just wrap the type into a dummy container and use value computation, then retrieve the type via decltype.

struct foo could be implemented like:

template<class T>
struct type_ {
    using type = T;
};

template<class T>
struct foo {
    auto constexpr static type_impl() {
        if constexpr (std::is_arithmetic<T>{}) {
            if constexpr (sizeof(T) < sizeof(void*)) {
                return type_<int>{};
            } else {
                return type_<float>{};
            }
        } else {
            return type_<double>{};
        }
    }

    using type = typename decltype(type_impl())::type;
};

static_assert(std::is_same<foo<incomplete_type>::type, double>{});
Landes answered 1/12, 2018 at 9:24 Comment(1)
Cool! I decide to rewrite some of my own traits with if constexprIterate
S
5

Your second attempt works if you wrap double in type_identity (which is a standard utility in C++20) and move ::type after std::conditional_t<...>:

template<typename T, bool>
struct foo_aux_aux
{
    using type = float;
};
template<typename T>
struct foo_aux_aux<T, true>
{
    using type = int;
};

template<typename T, bool = false>
struct foo_aux : foo_aux_aux<T, sizeof(T) < sizeof(void*)>
{};

template<typename T>
struct type_identity { using type = T; };

typename std::conditional_t<std::is_arithmetic_v<T>, foo_aux<T>, type_identity<double>>::type
Streetcar answered 1/12, 2018 at 9:46 Comment(3)
I like that you've simply adapted the OP's code to work instead of proposing a new solution.Birdsong
Thanks, man! I use type_identity heavily in my own codes, but this is the first time to its power expect for replacing using type =Iterate
foo_aux_aux is just a special case of conditional.Janiecejanifer
G
4

Not a great improvement, I suppose, but you can rewrite your third (working) attempt in a little less ugly (IMHO) way using decltype() and declaring (only) some functions.

I mean something as

struct incomplete_type;

constexpr float baz (std::false_type);
constexpr int baz (std::true_type);

template <typename>
constexpr double bar (std::false_type);

template <typename T>
constexpr auto bar (std::true_type)
 -> decltype(baz<std::bool_constant<(sizeof(T) < sizeof(void*))>{});

template<typename T>
struct foo
 { using type = decltype( bar<T>(std::is_arithmetic<T>{}) ); };
Goatskin answered 1/12, 2018 at 10:37 Comment(1)
This is the most elegant solution!Iterate
B
3

You can also use SFINAE:

template <class T1, class T2, class = int (*)[sizeof(T1) < sizeof(T2)]>
constexpr bool DeferSizeof(int) {
  return true;
}
template <class, class>
constexpr bool DeferSizeof(...) {
  return false;
}

template<typename T>
struct foo
{
    using type = std::conditional_t<std::is_arithmetic_v<T>,
        std::conditional_t<DeferSizeof<T, void *>(0), int, float>,
        double>;
};
Budde answered 1/12, 2018 at 9:37 Comment(1)
I tried void_t before posting this question, but failed somehow. Your SFINAE solution inspires me a lot, thanks!Iterate
M
2

With detection idiom from TS Library Fundamentals v2:

template <typename T>
using size_of = std::integral_constant<std::size_t, sizeof(T)>;

template <typename T>
struct foo
{
    using type = std::conditional_t<
        std::is_arithmetic_v<T>,
        std::conditional_t<
            std::experimental::detected_or_t<
                std::integral_constant<std::size_t, 0>, size_of, T
            >{} < sizeof(void*),
            int, float>,
        double>;
};

DEMO

Moralist answered 1/12, 2018 at 11:28 Comment(4)
I'm a big fan of Walter E. Brown, thanks for this solution!Iterate
detection idiom is not C++20.Janiecejanifer
@Janiecejanifer are there plans to integrate it into the standard library?Moralist
Nothing I'm aware of, and not likely with concepts already merged.Janiecejanifer

© 2022 - 2024 — McMap. All rights reserved.