17

As far as I know, std::to_integer<T> is equivalent to T(value) where value is a variable having type std::byte.
I looked into some implementations from the major compilers and found that in this case equivalent means literally implemented as. In other terms, most of the times to_integer is actually implemented as:

return T(value);

And that's all.

What I don't understand is what's the purpose of such a function?
Ironically the cons are even more than the pros. I should include a whole header for such a function just to avoid a C-like cast that is most likely directly inlined anyway.

Is there any other reason for that or it's just really a nice looking alternative for a C-like cast and nothing more?

skypjack
  • 49,335
  • 19
  • 95
  • 187
  • 4
    The header gripe is unfair. If you use `std::byte`, that header is already included (or better be!) – StoryTeller - Unslander Monica Jun 22 '19 at 22:14
  • 1
    `std::to_integer` is a `constexpr`, it's not necessarily the same for `T(value)`. – Silvano Cerza Jun 22 '19 at 22:16
  • 2
    @SilvanoCerza it's literally implemented as a constexpr function that returns `T(value)`. How can it be different from `T(value)`? I don't get it, but it sounds interesting and I invite you to formulate this better in an answer if possible. Thank you. – skypjack Jun 22 '19 at 22:22
  • I'm thinking it may have something to do with ADL but I'm not sure. – David G Jun 22 '19 at 22:24

4 Answers4

22

it's just really a nice looking alternative for a C-like cast and nothing more?

You say that as though it's some trivial detail.

Casts are dangerous. It's easy to cast something to the wrong type, and often compilers won't stop you from doing exactly that. Furthermore, because std::byte is not an integral type in C++, working with numerical byte values often requires a quantity of casting. Having a function that explicitly converts to integers makes for a safer user experience.

For example, float(some_byte) is perfectly legal, while to_integer<float>(some_byte) is explicitly forbidden. to_integer<T> requires that T is an integral type.

to_integer is a safer alternative.

I should include a whole header for such a function

If by "whole header", you mean the same header you got std::byte from and therefore is already by definition included...

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
10

std::to_integer<T>(some_byte) is equivalent to T(some_byte) if it actually compiles. T(some_byte) is equivalent to the unsafe C-style cast of (T)some_byte, which can do scary things. On the other hand, std::to_integer is appropriately constrained to only work when it is safe:

This overload only participates in overload resolution if std::is_integral_v<IntegerType> is true.

If the T was not actually an integer type, rather than potentially having undefined behavior, the code won't compile. If the some_byte was not actually a std::byte, rather than potentially having undefined behavior, the code won't compile.

Justin
  • 24,288
  • 12
  • 92
  • 142
  • Do you have an example of undefined behavior from a cast to integer type (especially from a non-pointer type, where perhaps an implementation could choose undefined behavior)? – Davis Herring Jun 22 '19 at 23:44
  • @DavisHerring I chose the phrase "potentially having undefined behavior" very carefully. If you perform an incorrect cast and use the result of that cast, you'll have undefined behavior. For example, if `T` was actually a pointer, if `some_byte` was actually a pointer, if one of them was a reference (I'd have to carefully consider the rules to know when the reference is correct). The point is that the interface for C-style casts lets you do dangerous and unsafe things, but `std::to_integer` does not. – Justin Jun 23 '19 at 00:20
  • @DavisHerring Also, implementations can't "choose undefined behavior"; that's not up to the implementation. The standard chooses whether some behavior is defined or not. The implementation cannot choose to make some behavior undefined. They can choose to define the undefined behavior, but they can't undefine defined behavior. – Justin Jun 23 '19 at 00:21
  • Expressions never have reference type, and a cast to a non-reference type is concerned with the value category of the source only if it calls a function. As for “implementation-defined undefined behavior”, I said “perhaps” for much the same reason as you said “potentially”—an implementation could plausibly say that converting an integer to a pointer involved reading through the (potentially invalid) pointer or so. – Davis Herring Jun 23 '19 at 00:33
4

Beyond the expression of intent and safety issues already mentioned, I get the idea from the committee discussion on the paper that it’s meant to be like std::to_string and might have more overloads in the future.

Davis Herring
  • 36,443
  • 4
  • 48
  • 76
  • 1
    This is an excellent point. It could be improved by quoting some of the committee discussion and explaining how the discussion implies this, but it makes sense that future evolution is a reason for `std::to_integer`. – Justin Jun 23 '19 at 00:26
  • 1
    @Justin: The precise meeting minutes aren’t to be published, but there isn’t much to quote anyway (beyond the comparison to `to_string`)—thus the “I get the idea”. I think it’s also just possible that alternative designs that didn’t support `static_cast(myByte)` were still being considered. – Davis Herring Jun 23 '19 at 00:43
3

A C style cast is not equivalent to std::to_integer<T>. See the below example.

std::to_integer<T> only participates in overload resolution if std::is_integral_v<T> is true.

#include <cstddef>
#include <iostream>

template <typename T>
auto only_return_int_type_foo(std::byte& b)
{
    return std::to_integer<T>(b);
}

template <typename T>
auto only_return_int_type_bar(std::byte& b)
{
    return T(b);
}

int main()
{
    std::byte test{64};
    // compiles
    std::cout << only_return_int_type_foo<int>(test) << std::endl;

    // compiler error
    std::cout << only_return_int_type_foo<float>(test) << std::endl;

    // compiles
    std::cout << only_return_int_type_bar<int>(test) << std::endl;

    // compiles
    std::cout << only_return_int_type_bar<float>(test) << std::endl;
} 
Fibbs
  • 1,350
  • 1
  • 13
  • 23