7

Let's say I have function overloads for all standard integer types:

void foo( char );
void foo( signed char );
void foo( short );
void foo( int );
void foo( long );
void foo( long long );
// .... all unsigned variants as well

Is it possible that those overloads would fail to find proper overload for types like int8_t or something like that? Is there a portable way to handle such overloads?

How about references?

To clarify the question: it comes from discussion of this question Why is int8_t read as a character? and claims that there could be compiler generated integer types that would be not an alias to fundamental C++ types. So in such case overloads for all fundamental case may not accept it. On another way I cannot provide overload for int8_t as on many platforms it is just an alias and will get error for redefinition of existing overload.

Community
  • 1
  • 1
Slava
  • 43,454
  • 1
  • 47
  • 90
  • You can define the function for the type int8_t as deleted. – Vlad from Moscow Jan 09 '17 at 16:31
  • 1
    Instead of saying "all", can you produce an explicit list of what you are overloading? (saying "`signed` and `unsigned` of each of:" to halve it is ok). Because what you call "all" may not be "all". – Yakk - Adam Nevraumont Jan 09 '17 at 16:31
  • 1
    Depends on your definition of "accept". You can pass an `int8_t` to a function defined as taking an `int` and it will accept it just fine. – melpomene Jan 09 '17 at 16:31
  • 6
    @VladfromMoscow But if `int8_t` is `signed char`, you'll get an error because you deleted an overload you also defined. – Yakk - Adam Nevraumont Jan 09 '17 at 16:32
  • @melpomene If you have a `signed char` and `signed int` overload and you pass `int8_t`, which overload is called, and is it guaranteed the same by the standard regardless of compiler choices? Does the OP care? But that is a good question; what does the OP mean by "accept". – Yakk - Adam Nevraumont Jan 09 '17 at 16:33
  • @Yakk And what is the problem? This argument shall not be accepted by the overloaded functions. – Vlad from Moscow Jan 09 '17 at 16:34
  • 1
    @VladfromMoscow: He *wants* it to be accepted by the overloaded functions. That's his point. – Nicol Bolas Jan 09 '17 at 16:40
  • How could they not accept it? Can you provide an example? – melpomene Jan 09 '17 at 16:44
  • @Yakk there is claim that compiler is allowed to generate new integer types (see question in link) and that such types will not be accepted by any of such overloads (compilation) error. – Slava Jan 09 '17 at 16:46
  • 3
    It is amazing how many stupid anonymous downvoters on SO that do not bother to understand the question. – Slava Jan 09 '17 at 16:53
  • @Slava They will be accepted if there is a preferred conversion sequence? So again, what do you mean by "accept". Please be explicit. – Yakk - Adam Nevraumont Jan 09 '17 at 18:18
  • @Slava So, now you mean "a function taking references to all standard integer types", which is not what you asked *in the question*? Again, be explicit, and as concrete as you can be, in what you want. Put this *in the question*. Actual code is going to help; lack of code is going to hurt. It is really, really hard to be precise in only English when talking about code. Your "is there any portable way to handle such overloads" implies you want a solution, but your problem is too vague to find such a good solution from the description. – Yakk - Adam Nevraumont Jan 09 '17 at 18:21
  • @Yakk I cannot be concrete as I always was in impression that overloads for fundamental types would handle all types. And now when I realize that it is not the fact I try to understand what consequences it brings. Plus documentation of `std::basic_ostream` and more important `std::basic_istream` (as it works with references) made me think this is not possible. – Slava Jan 09 '17 at 18:25
  • @Slava "suppose we have a set of overloads `bob`, with `void bob(signed int) {} void bob(signed long) {} void bob(signed char) {}`" is an example of being explicit (where you may go on to write out every overload, or describe it explicitly enough that you can skip some "and similarly for unsigned"). Then you can explicitly state what you mean by "fails to accept" an `int8_t` ("could there be a case where `bob( std::int8_t(0) )` is ambiguous or fails to find a valid overload?"). That may or may not be what you mean by "accept", but **I cannot tell what you mean** unless you are clear. – Yakk - Adam Nevraumont Jan 09 '17 at 18:28
  • @Yakk suppose we have these functions - http://en.cppreference.com/w/cpp/io/basic_istream/operator_gtgt now could be there a case that `operator>>` fails to find a valid overload for `intXX_t` types? – Slava Jan 09 '17 at 18:34
  • @Slava Ok, write that in the question (but don't use the contents of a link as an essential part of the question, questions should stand alone). Include as concrete an example of "fails to find an overload" (declair a variable, pass it in). Maybe remove the reference to `istream` entirely, just list the overloads. Your question as written is too vague, clarifying what you mean may avoid it being closed. – Yakk - Adam Nevraumont Jan 09 '17 at 18:39

4 Answers4

11

The standard does not provide a guarantee that the standard integer types are the only integer types supported by the compiler. Indeed, the C and C++ standards explicitly give permission to compilers to define other integer types, collectively known as "extended integer types":

There may also be implementation-defined extended signed integer types.

Likewise, for each of the extended signed integer types there exists a corresponding extended unsigned integer type ...

the extended signed integer types and extended unsigned integer types are collectively called the extended integer types.

And the C standard does not prohibit an implementation from using extended integer types to implement the stdint.h types. There is even this non-normative notation, just to make it clear:

Some of these types may denote implementation-defined extended integer types.

If you want a function that can take any integer type, you have two options: make it a template (probably using SFINAE to disallow non-integral types) or provide a single overload that takes std::intmax_t. The standard requires that intmax_t is the largest integer supported:

designates a signed integer type capable of representing any value of any signed integer type

And of course "signed integer type" includes standard and extended types. So std::intmax_t can handle any signed integer that the implementation supports.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • "Indeed, the C and C++ standards explicitly give permission to compilers to define other integer types" do you mind to quote standard? – Slava Jan 09 '17 at 16:48
  • You mention C standard and do not explicitly say from which standard is first quote. The question is on C++, not C though. (and there is no function overloading on C as much as I know) – Slava Jan 09 '17 at 17:03
  • @Slava: C++'s `cstdint`'s behavior is defined entirely by reference to the C standard. So if you want to know the behavior of those types, you *have* to look at the C standard. As for the other quotes, most of them could be from either C or C++, because they use almost identical language. – Nicol Bolas Jan 09 '17 at 17:05
  • This brings another interesting question - http://stackoverflow.com/questions/41554125/how-standard-c-input-output-stream-have-to-handle-intxx-t-types – Slava Jan 09 '17 at 18:06
  • gcc provides `__int128_t` while `intmax_t` is identical to `int64_t` on 64bit machines ([live demo](http://melpon.org/wandbox/permlink/IXrEFKuryiioKDCk)). – Claas Bontus Jan 10 '17 at 11:45
  • @ClaasBontus: Then GCC is doing something wrong. `intmax_t` is required to be "capable of representing any value of any signed integer type". So GCC is in violation of the C and C++ standards, since `__int128_t` can contain integer values larger than `intmax_t`. – Nicol Bolas Jan 10 '17 at 14:55
  • @NicolBolas _"any signed integer type"_ Doesn't this refer only to _language/library_ types? How can this hold for, say, some multiprecision library types, where the precision/bit width is not limited (such as in Boost.Multiprecision)? – Daniel Langr Dec 30 '22 at 12:04
  • @DanielLangr: "*Doesn't this refer only to language/library types?*" Um, yes. That's kind of the point of the question and answer. – Nicol Bolas Dec 30 '22 at 15:02
  • @NicolBolas My point was that you wrote _"So GCC is in violation of the C and C++ standards"_. I don't think so, since `__int128_t` is not a C++ type. – Daniel Langr Jan 02 '23 at 08:08
  • @DanielLangr: That depends. If `is_integral_v<__int128_t>` is true, that means `__int128` is an extended integer type. And `intmax_t` is required to be big enough to contain *any* integer type, standard or extended. I checked, and GCC doesn't declare it to be an integral type, so `intmax_t` isn't required to include it. – Nicol Bolas Jan 02 '23 at 14:30
1

int8_t will implicitly convert to int (and in fact when taking by value any signed integral type will convert to intmax_t if there is a function taking this type. And in general integral types implicitly convert between each other (I have investigated a weird issue where I provided overload for both int and long types [they had different sizes on that particular compiler] and the C++ compiler complained that the overload resolution would be ambiguous)

Also, usually int8_t and signed char are equivalent.

Paul Stelian
  • 1,381
  • 9
  • 27
  • 2
    Question is not what is usually but is there guarantee that overloads for all fundamental types will handle all integer types – Slava Jan 09 '17 at 16:38
  • A complex integer (complex number with both components integer) and any custom type is not integral. Only all variants of int and char, plus typedefs such as int8_t, uint32_t, are considered integral types, and they all implicitly convert between each other (even if forced to be lossy it is still implicit) – Paul Stelian Jan 09 '17 at 16:57
  • 2
    Looks like you do not understand the question. I am not asking for user defined types. Question is "if compiler allowed to create integer types that would not be handled by overloads for all fundamental types" – Slava Jan 09 '17 at 17:01
  • All builtin integer types convert implicitly between each other. Even if it can be lossy. – Paul Stelian Jan 09 '17 at 17:04
  • This is not the case for function overloading. – Slava Jan 09 '17 at 17:05
  • @Slava Then please explain to me why a function taking int and a function taking long conflict even on platforms where the two types have different sizes – Paul Stelian Jan 09 '17 at 17:42
  • 2
    Most probably you are doing something wrong, but that is unrelated to this question and you probably should create your own. – Slava Jan 09 '17 at 17:43
1

There is no guarantee that every platform will define the fixed-width integer types the same way, or that they'll be defined in terms of a fundamental type on any given platform. Therefore, to ensure that your overloads will catch fixed-width types on any given platform, you need to determine how they're implemented on that platform.

While you could do this with template tricks, the simplest way to do it is simply to use a distinct, minimal helper program, that outputs the typeid of each fixed-width and fundamental type, allowing you to compare their internal names to determine how each fixed-width type is implemented on that platform & with that compiler.

#include <cstdint>
#include <iostream>
#include <iomanip>
#include <typeinfo>

int main() {
    std::cout << std::left;
    std::cout << std::setw(10) << "int8_t: "   << typeid(int8_t).name()   << '\n'
              << std::setw(10) << "int16_t: "  << typeid(int16_t).name()  << '\n'
              << std::setw(10) << "int32_t: "  << typeid(int32_t).name()  << '\n'
              << std::setw(10) << "int64_t: "  << typeid(int64_t).name()  << '\n'
              << std::setw(10) << "uint8_t: "  << typeid(uint8_t).name()  << '\n'
              << std::setw(10) << "uint16_t: " << typeid(uint16_t).name() << '\n'
              << std::setw(10) << "uint32_t: " << typeid(uint32_t).name() << '\n'
              << std::setw(10) << "uint64_t: " << typeid(uint64_t).name() << '\n'
              << std::endl;

    std::cout << std::setw(20) << "char: "               << typeid(char).name()               << '\n'
              << std::setw(20) << "signed char: "        << typeid(signed char).name()        << '\n'
              << std::setw(20) << "unsigned char: "      << typeid(unsigned char).name()      << '\n'
              << std::setw(20) << "signed short: "       << typeid(signed short).name()       << '\n'
              << std::setw(20) << "unsigned short: "     << typeid(unsigned short).name()     << '\n'
              << std::setw(20) << "signed int: "         << typeid(signed int).name()         << '\n'
              << std::setw(20) << "unsigned int: "       << typeid(unsigned int).name()       << '\n'
              << std::setw(20) << "signed long: "        << typeid(signed long).name()        << '\n'
              << std::setw(20) << "unsigned long: "      << typeid(unsigned long).name()      << '\n'
              << std::setw(20) << "signed long long: "   << typeid(signed long long).name()   << '\n'
              << std::setw(20) << "unsigned long long: " << typeid(unsigned long long).name() << '\n'
              << std::endl;
}

Example output:

// MSVC 2015 (x86 & x64):
int8_t:   signed char
int16_t:  short
int32_t:  int
int64_t:  __int64
uint8_t:  unsigned char
uint16_t: unsigned short
uint32_t: unsigned int
uint64_t: unsigned __int64

char:               char
signed char:        signed char
unsigned char:      unsigned char
signed short:       short
unsigned short:     unsigned short
signed int:         int
unsigned int:       unsigned int
signed long:        long
unsigned long:      unsigned long
signed long long:   __int64
unsigned long long: unsigned __int64

// Clang & GCC (x64):
int8_t:   a
int16_t:  s
int32_t:  i
int64_t:  l
uint8_t:  h
uint16_t: t
uint32_t: j
uint64_t: m

char:               c
signed char:        a
unsigned char:      h
signed short:       s
unsigned short:     t
signed int:         i
unsigned int:       j
signed long:        l
unsigned long:      m
signed long long:   x
unsigned long long: y

From this:

  • MSVC (on both x86 & x64 Windows, since ILP32 & LLP64 use the same type sizes): We can see that fixed-width types are based on fundamental types; note that for 64-bit integral types, the compiler will emit the internal name __int64 (a synonym from before long long was standardised, as Bo Persson pointed out in the comments) instead of the canonical name long long
  • Clang & GCC (on x64 *nix): By comparing mangled type names, we can see that:
    • The 8-bit types are based on the char types.
    • The 16-bit types are based on the short types.
    • The 32-bit types are based on the int types.
    • The 64-bit types are based on the long types.

From this, we know that on these platforms, with these compilers:

  • signed char will catch int8_t.
  • unsigned char will catch uint8_t.
  • signed short will catch int16_t.
  • unsigned short will catch uint16_t.
  • And so on...
  • [Note that due to differences between Windows' LLP64 and *nix's LP64 model, int64_t & uint64_t will be caught by different overloads.]

You can then apply this process with any other platforms and compilers you may wish to use, allowing you to detect when you need to make platform-specific modifications.


This seems like an XY problem, though. Your main issue is that you want to be able to overload for the fixed-width types on platforms where they're distinct from the fundamental types, but don't know how to do this without breaking your code on platforms where they're the same as the fundamental types.

To do this, you'll need to find a way to uniquely identify the platform you're on; the easiest way to do this is by looking for macros specific to that platform.

void func(          char);
void func(  signed  char);
void func(unsigned  char);
void func(         short);
void func(unsigned short);
// ...

#ifdef PLATFORM_WHERE_INT8_T_ISNT_SIGNED_CHAR
    void func( int8_t);
    void func(uint8_t);
#endif

#ifdef PLATFORM_WHERE_INT16_T_ISNT_SIGNED_SHORT
    void func( int16_t);
    void func(uint16_t);
#endif

// ...

Using a setup like that, you can define additional overloads for platforms where the fixed-width types aren't fundamental types, without affecting the platforms where they are fundamental types. This would allow you to do any platform-specific handling, before handing them off to one of your fundamental type overloads (if necessary).

Community
  • 1
  • 1
0

Is it possible that those overload will not accept types like int8_t or something like that?

No, not for int8_t. Even if int8_t isn't an alias of one of the standard integer types, it is still convertible to other integer types, and promotion to int is unambiguously preferred. So, a call with an extended int8_t would resolve to one of the overloads.

However, for larger types, this is still an issue.

eerorika
  • 232,697
  • 12
  • 197
  • 326