The standard way to get sizeof(promoted(x)) [duplicate] - c++

This question already has answers here:
Type trait to obtain default argument promotions
(3 answers)
Closed 7 years ago.
Is there a standard way to get the size of the type a variable would be promoted to when passed as a variadic argument?
auto x = ...;
auto y = sizeof(promoted(x));
The results should be:
char -> sizeof(int)
int -> sizeof(int)
float -> sizeof(double)
...

auto s = sizeof(+x);
should do the trick for integers.
+x makes use of the unary + operator, which performs integer promotion just like any other arithmetic operator.
I am not aware of any standard promotion rules for float that would apply here (in the integer promotion sense) since you can do arithmetic with them without promoting. If you always want to promote to at least double you can try
auto s = sizeof(x + 0.);
and then distinguish between floating point and integers before you get there.
Again, I do not think that you can handle integers and floating points at once because of the different meanings of "promotion" we are applying here.

We can simply declare overloaded promoted functions with the proper types:
int promoted(char);
int promoted(short);
int promoted(int);
long promoted(long);
long long promoted(long long);
double promoted(float);
double promoted(double);
long double promoted(long double);
Note that the functions need no implementations, because we are never actually calling them.
Here is a simple test run which prints 1, 4 and 4, 8 on my machine:
std::cout << sizeof('a') << '\n';
std::cout << sizeof(promoted('a')) << '\n';
std::cout << sizeof(3.14f) << '\n';
std::cout << sizeof(promoted(3.14f)) << '\n';

To generalise Baum mit Augen's answer, you could write function templates like this:
template <typename T>
auto promoted(T)
-> std::enable_if_t<std::is_integral<T>::value, decltype(+T{})>;
template <typename T>
auto promoted(T)
-> std::enable_if_t<std::is_floating_point<T>::value, decltype(T{}+0.)>;
//usage
sizeof(promoted(a))
Or a version using type traits:
template <typename T, typename = void>
struct promoted;
template <typename T>
struct promoted<T, std::enable_if_t<std::is_integral<T>::value>>
{ using type = decltype(+T{}); };
template <typename T>
struct promoted<T, std::enable_if_t<std::is_floating_point<T>::value>>
{ using type = decltype(T{} + 0.); };
template <typename T>
using promoted_t = typename promoted<T>::type;
//usage
sizeof(promoted_t<decltype(a)>)

Related

Making a function in a struct template [duplicate]

This question already has answers here:
How to define a template member function of a template class [duplicate]
(2 answers)
Why can templates only be implemented in the header file?
(17 answers)
Closed 7 months ago.
So i made a template struct cause i want to be able to decide what type i give to my val. But when creating a function i don't know how to do it.
Here's what i'm doing:
In my .hpp
template<typename T>
struct Integer
{
T val;
void setUint(const T &input);
};
Now i can set what variable i want in the val and what i want in the function.
But now in my cpp i don't know how to invoke the function.
void Integer<T>::setUint(const T &input)
{
val = input;
}
Error: identifier "T" is undefined.
A template function is a way to operate with generic types (you may consider the type as an argument). Your template parameter T allows to pass different types to a function when you invoke the function (which means, simply said, you may replace T with some other types int, double, ...)
Please, have a look at the following simple example.
#include <iostream>
#include <typeinfo>
// you may put it all in a hpp and thus include the hpp file in the CPP
template<typename T>
struct Integer
{
T val;
void setUint(const T &input){
val=input;
std::cout <<"the type is " << typeid(T).name() << " value is "<< val << std::endl;
}
};
// or look at Jarod42's implementation details.
/*
template<typename T>
void Integer<T>::setUint(const T &input){
val=input;
std::cout <<"the type is " << typeid(T).name() << " value is "<< val << std::endl;
}*/
// and here you have your cpp calling you template function with different types
int main()
{
Integer<double> value;
value.setUint(1500000);
Integer<int> value2;
value2.setUint(5);
return 0;
}
Syntax is
template <typename T>
void Integer<T>::setUint(const T &input)
{
val = input;
}
Also for templates, check if T actually matches your expectations. Your template will not make sense for types that are not Integers. Example on how to add these constraints here : https://godbolt.org/z/YsneKe7vj (both C++17 SFINAE, and C++20 concept)
#include <type_traits>
#include <string>
//-----------------------------------------------------------------------------------------------------------
// for C++17 setup a constexpr that will evaluate (at compile time, that's the constexpr bit)
// if a given type is an integer type
template<typename type_t>
constexpr bool is_integer_type_v = std::is_integral_v<type_t> && !std::is_same_v<type_t,bool>;
// create a template that can optionally be enabled. This is an example of a technique called SFINAE
template<typename type_t, typename enable = void>
struct Integer;
// for C++ define a specialization that will succesfully be enabled of integer types only
template<typename type_t>
struct Integer<type_t,std::enable_if_t<is_integer_type_v<type_t>>>
{
void Set(const type_t v)
{
value = v;
}
type_t value;
};
//-----------------------------------------------------------------------------------------------------------
// for C++20 use concepts
template<typename type_t>
concept is_integer = std::is_integral_v<type_t> && !std::is_same_v<type_t, bool>;
// note is_integer now replaces typename, and it can only accept types
// that satisfy the concept
template<is_integer integer_t>
struct IntegerCpp20
{
void Set(const integer_t v)
{
value = v;
}
integer_t value;
};
//-----------------------------------------------------------------------------------------------------------
int main()
{
//Integer<std::string> value; // will not compile;
Integer<int> value;
value.Set(2);
//IntegerCpp20<std::string> value; // will not compile;
IntegerCpp20<int> value20;
value20.Set(20);
return 0;
}

C++ template function that takes float or double

I want to write a template function that will work on both double and float, something like that:
template <typename T>
constexpr auto someCalc(const T x) {
return x * 4.0 + 3.0;
}
My issue is that both 4.0 and  3.0 are doubles literal, thus I get the following error on clang:
error: implicit conversion increases floating-point precision: 'const float' to 'double' [-Werror,-Wdouble-promotion]
Is there an elegant way to write this code without having the up-conversion to double? The best I can come up with, is this
template <typename T>
constexpr auto someCalc(const T x) {
constexpr T four = 4.0;
constexpr T three = 3.0;
return x * four + three;
}
which I find less readable and harder to maintain for larger/more complicated function.
I would do it this way:
template <typename T>
constexpr auto someCalc(const T x) {
return x * T(4.0) + T(3.0);
}
Or with static_cast, if you prefer it.
While HolyBlackCat's answer is completely viable, here's my two bits:
Your constants are supposed to mean something in that particular situation. They likely are magic numbers with some meaning. The best course of actions is to give them a name, if you want make them maintenable. And in newer C++ it allows them to be templates
#include <iostream>
namespace sdd // some_dirty_details
{
/// #brief: Description of the constant
template <class T>
constexpr T some_constant = static_cast<T>(3.0);
}
int main()
{
std::cout << sdd::some_constant<float> << std::endl;
}

C++: Determine the integral conversion rank of a given integral type at compile time?

In a project that I am working on, I have a data structure that can store numbers of various types and needs to make guarantees about not accidentally losing precision / information.
Because standard C++ allows some lossy conversions to occur implicitly, I made a type trait that I use to determine what conversions I will allow and forbid the ones that I don't like using SFINAE.
I realized there is a subtle issue in how I am doing this though.
Here's a code excerpt:
// If it is between two floating point types, no truncation is allowed.
template <typename A, typename B>
struct safe_numeric<A,
B,
typename std::enable_if<(std::is_floating_point<A>::value &&
std::is_floating_point<B>::value)>::type> {
static constexpr bool value = sizeof(A) >= sizeof(B);
};
The intention is that, float can be promoted to double or long double, and double can be promoted to long double, but it can't get smaller.
However, using sizeof here is bad because on a machine where sizeof(double) == sizeof(float), it would be legal to convert double to float according to this template. This will make my program non-portable -- a developer may write code that compiles and work great on their machine, and as a point of fact, not be losing precision. But on some other machine, it may just fail to compile and complain about unsafe conversions, potentially.
What I really want is to obtain the conversion rank as described by the standard in [4.13] [conv.rank], for instance. I would like to do this for both floating point and integral types.
For instance I could just roll my own:
template <typename T>
struct conversion_rank;
template <>
struct conversion_rank<float> {
static constexpr std::size_t value = 0;
};
template <>
struct conversion_rank<double> {
static constexpr std::size_t value = 1;
};
template <>
struct conversion_rank<long double> {
static constexpr std::size_t value = 2;
};
And use that metafunction instead of sizeof.
If I want to do this for integral types... there are many more of them.
Also there is the issue that the integral types are not required to exist, so I might want to try to detect that scenario so that the conversion_rank template will compile.
Is there a better / standard way to approach this? I didn't find a "conversion_rank" metafunction in the standard library, and I guess the standard doesn't actually give numeric values, it just specifies "higher" and "lower". But maybe there is something in boost / some other strategy that is less laborious and doesn't rely on sizeof?
I open-sourced the component that I ended up using for this.
The code that I used to make ranks is like this (see here):
enum class numeric_class : char { integer, character, wide_char, boolean, floating };
template <typename T, typename ENABLE = void>
struct classify_numeric;
template <typename T>
struct classify_numeric<T, typename std::enable_if<std::is_integral<T>::value>::type> {
static constexpr numeric_class value = numeric_class::integer;
};
#define CLASSIFY(T, C) \
template <> \
struct classify_numeric<T, void> { \
static constexpr numeric_class value = numeric_class::C; \
}
CLASSIFY(char, character);
CLASSIFY(signed char, character);
CLASSIFY(unsigned char, character);
CLASSIFY(char16_t, character);
CLASSIFY(char32_t, character);
CLASSIFY(wchar_t, wide_char);
CLASSIFY(bool, boolean);
CLASSIFY(float, floating);
CLASSIFY(double, floating);
CLASSIFY(long double, floating);
#undef CLASSIFY
template <typename T>
struct rank_numeric;
#define RANK(T, V) \
template <> \
struct rank_numeric<T> { \
static constexpr int value = V; \
}
#define URANK(T, V) \
RANK(T, V); \
RANK(unsigned T, V)
RANK(bool, 0);
RANK(signed char, -1);
URANK(char, 0);
RANK(char16_t, 1);
RANK(char32_t, 2);
URANK(short, 1);
URANK(int, 2);
URANK(long, 3);
URANK(long long, 4);
RANK(float, 0);
RANK(double, 1);
RANK(long double, 2);
#undef RANK

How to use generic function in c++?

I have written this code in which if I uncomment the 2nd last line I get error - "template argument deduction/substitution failed: ". Is it because of some limit to generic functions in C++? Also my program doesn't print floating answer for the array b. Is there anything I can do for that? (sorry for asking 2 questions in single post.)
P.S: I have just started learning C++.
#include <iostream>
using namespace std;
template <class T>
T sumArray( T arr[], int size, T s =0)
{
int i;
for(i=0;i<size;i++)
{ s += arr[i];
}
return s;
}
int main()
{
int a[] = {1,2,3};
double b[] = {1.0,2.0,3.0};
cout << sumArray(a,3) << endl;
cout << sumArray(b,3) << endl;
cout << sumArray(a,3,10) << endl;
//cout << sumArray(b,3,40) << endl; //uncommenting this line gives error
return 0;
}
EDIT 1: After changing 40 to 40.0, the code works. Here is the output I get:
6
6
16
46
I still don't get the floating answer in 2nd case. Any suggestion ?
The reason is that compiler can not deduce the type for T.
How it should understand what T is for your last example? The type of the first argument (b) is double[], while it is T[] in the function definition. Therefore it looks like that T should be double. However, the type of the third argument (40) is int, so it looks like T should be int. Hence the error.
Changing 40 to 40.0 makes it work. Another approach is to use two different types in template declaration:
#include <iostream>
using namespace std;
template <class T, class S = T>
T sumArray( T arr[], int size, S s =0)
{
int i;
T res = s;
for(i=0;i<size;i++)
{ res += arr[i];
}
return res;
}
int main()
{
int a[] = {1,2,3};
double b[] = {1.0,2.0,3.1};
cout << sumArray(a,3) << endl;
cout << sumArray(b,3) << endl;
cout << sumArray(a,3,10) << endl;
cout << sumArray(b,3,40) << endl; //uncommenting this line gives error
return 0;
}
Note that I had to cast s to T explicitly, otherwise the last example will lose fractional part.
However, this solution will still not work for sumArray(a,3,10.1) because it will cast 10.1 to int, so if this is also a possible use case, a more accurate treatment is required. A fully working example using c++11 features might be like
template <class T, class S = T>
auto sumArray(T arr[], int size, S s=0) -> decltype(s+arr[0])
{
int i;
decltype(s+arr[0]) res = s;
...
Another possible improvement for this template function is auto-deduction of array size, see TartanLlama's answer.
sumArray(b,3,40)
The type of 40 is int, but the type of b is double[3]. When you pass these in as arguments, the compiler gets conflicting types for T.
A simple way to fix this is to just pass in a double:
sumArray(b,3,40.0)
However, you would probably be better off allowing conversions at the call site by adding another template parameter. You can also add one to deduce the size of the array for you so that you don't need to pass it explicitly:
template <class T, class U=T, std::size_t size>
U sumArray(T (&arr) [size], U s = 0)
The U parameter is defaulted to T to support the default value for s. Note that to deduce the size of the array, we need to pass a reference to it rather than passing by value, which would result in it decaying to a pointer.
Calling now looks like this:
sumArray(b,40)
Live Demo
In
template <class T>
T sumArray( T arr[], int size, T s =0)
^ ^
Both (deducible) T should match.
In sumArray(b, 3, 40), it is double for the first one, and int for the second one.
There is several possibilities to fix problem
at the call site, call sumArray(b, 3, 40.0) or sumArray<double>(b, 3, 40);
Use extra parameter:
template <typename T, typename S>
auto sumArray(T arr[], int size, S s = 0)
Return type may be T, S, or decltype(arr[0] + s) depending of your needs.
make a parameter non deducible:
template <typename T> struct identity { using type = T;};
// or template<typename T> using identity = std::enable_if<true, T>;
template <typename T>
T sumArray(T arr[], int size, typename identity<T>::type s = 0)
Should be
sumArray(b,3,40.0)
so, T will be deduced to double. In your code it's int.
Another option when deduction fails is to explicitly tell the compiler what you mean:
cout << sumArray<double>(b,3,40) << endl;
The compiler does not know whether T should be int or double.
You might want to do the following, in order to preserve the highest precision of the types passed:
template <class T, class S>
std::common_type_t <T, S> sumArray (T arr [], std::size_t size, S s = 0)
{
std::common_type_t <T, S> sum = s;
for (std::size_t i = 0; i != size; ++i)
{
sum += arr[i];
}
return sum;
}
The function you are writing, however, already exists. It's std::accumulate:
std::cout << std::accumulate (std::begin (b), std::end (b), 0.0) << std::endl;
Templates only accept one type of data, for example if you send an array of double, then the runtime will deduce :
Template = double[]
so every time he will see it he will expect an array of doubles.
sumArray(b,3,40) passes "b" (which is an array of doubles) but then you pass "40" which the runtime cannot implicitly convert to double.
So the code
sumArray(b,3,40.0)
will work

Return type of decltype when applied to ternary(?:) expression

When I look into a code snippet for a possible implementation of std::common_type
template <class ...T> struct common_type;
template <class T>
struct common_type<T> {
typedef decay_t<T> type;
};
template <class T, class U>
struct common_type<T, U> {
typedef decay_t<decltype(true ? declval<T>() : declval<U>())> type;
};
template <class T, class U, class... V>
struct common_type<T, U, V...> {
typedef common_type_t<common_type_t<T, U>, V...> type;
};
The part how to get a common type for two template argument makes me confused.It is a usage of ternary operator with decltype.
As I known, whether to return the second or third operand is decided by the value of first operand. In this snippet, the first operand is true which means the return value of expression will always be declval<T>(). If it is what i thought which make no sense... Therefore, I have tried the following test
int iii = 2;
float fff = 3.3;
std::cout << typeid(decltype(false? std::move(iii):std::move(fff))).name() << std::endl;
std::cout << typeid(decltype(std::move(iii))).name() << std::endl;
std::cout << typeid(decltype(false ? iii : fff)).name() << std::endl;
std::cout << typeid(decltype(true ? iii : fff)).name() << std::endl;
// [02:23:37][ryu#C++_test]$ g++ -std=c++14 -g common_type.cpp
// output
// f
// i
// f
// f
Comparing with the running result, The result what i though should be like as follows
int iii = 2;
float fff = 3.3;
std::cout << typeid(decltype(false ? iii : fff)).name() << std::endl; // should return f;
std::cout << typeid(decltype(true ? iii : fff)).name() << std::endl; // should return i;
Anyone when can help to explain why the running result is different ?
In other words, what's the return result of decltype when it is applied on a ternary expression?
The type of an expression is a compile-time property. The value of the first operand in a conditional expression (and hence the branch selected), is, in general, a run-time thing, so it can't possibly affect the expression's type.
Instead, a complicated set of rules (more than a page of standardese, most of which I quoted in this answer) is used to determine what the "common type" of the second and third operands is, and the conditional expression is of that type. std::common_type merely leverages the existing rules in the core language.