Home » Community » U++ community news and announcements » get_i
| get_i [message #54247] |
Sun, 14 June 2020 19:10  |
 |
mirek
Messages: 14290 Registered: November 2005
|
Ultimate Member |
|
|
A complemement to findarg and decode:
get_i(-1, "zero", "one", "two") = zero
get_i(0, "zero", "one", "two") = zero
get_i(2, "zero", "one", "two") = two
get_i(3, "zero", "one", "two") = two
|
|
|
|
| Re: get_i [message #54250 is a reply to message #54247] |
Tue, 16 June 2020 17:45   |
Novo
Messages: 1431 Registered: December 2006
|
Ultimate Contributor |
|
|
Thank you.
I played a little bit with get_i and godbolt.org and got results below.
Test: const char* c = get_i(-1, "zero", "one", "two");
Assembly for the original code (-O2):
.LC0:
.string "zero"
_GLOBAL__sub_I_c:
mov QWORD PTR c[rip], OFFSET FLAT:.LC0
ret
c:
.zero 8
I changes U++ code a little bit:
template <class T> constexpr const T& min(const T& a, const T& b) { return a < b ? a : b; }
template <class T> constexpr const T& max(const T& a, const T& b) { return a > b ? a : b; }
template <class T> // deprecated name, use clamp
constexpr T minmax(T x, T _min, T _max) { return min(max(x, _min), _max); }
template <class T>
constexpr T clamp(T x, T _min, T _max) { return minmax(x, _min, _max); }
inline constexpr const char *get_i(int i, const char *p0, __List##I(E__NFValue)) \
Resulting assenbly:
.LC0:
.string "zero"
c:
.quad .LC0
Conclusion: "constexpr" is quite useful ...
Regards,
Novo
|
|
|
|
|
|
|
|
| Re: get_i [message #54254 is a reply to message #54253] |
Tue, 16 June 2020 18:21   |
Novo
Messages: 1431 Registered: December 2006
|
Ultimate Contributor |
|
|
Another experiment/suggestion.
I rewrote get_i using variadic template:
template <typename A, typename... T>
constexpr A get_i(int i, const A& p0, const T& ...args)
{
A list[] = {p0, args...};
int n = sizeof...(args);
return list[clamp(i, 0, n)];
}
const char* cr = get_i(1, "zero", "one", "two");
RDUMP(cr);
int ir = get_i(1, 0, 1, 2);
RDUMP(ir);
IMHO, my implementation is much shorter and it will compile faster.
IMHO, macroses __List and __Expand are not needed anymore ...
Regards,
Novo
|
|
|
|
| Re: get_i [message #54256 is a reply to message #54254] |
Tue, 16 June 2020 21:20   |
 |
mirek
Messages: 14290 Registered: November 2005
|
Ultimate Member |
|
|
Novo wrote on Tue, 16 June 2020 18:21Another experiment/suggestion.
I rewrote get_i using variadic template:
template <typename A, typename... T>
constexpr A get_i(int i, const A& p0, const T& ...args)
{
A list[] = {p0, args...};
int n = sizeof...(args);
return list[clamp(i, 0, n)];
}
const char* cr = get_i(1, "zero", "one", "two");
RDUMP(cr);
int ir = get_i(1, 0, 1, 2);
RDUMP(ir);
IMHO, my implementation is much shorter and it will compile faster.
IMHO, macroses __List and __Expand are not needed anymore ...
Yes, you are right about this, I have used old tricks mostly out of habit. I guess I will have to rewrite it all now 
However, constexpr I still do not agree. Following your logic, we should add constexpr to every single function everywhere - these are as likely to have constant parameters as get_i (which has like 0.00000001% chance that first parameter will be const in real code).
|
|
|
|
| Re: get_i [message #54257 is a reply to message #54254] |
Tue, 16 June 2020 21:22   |
Novo
Messages: 1431 Registered: December 2006
|
Ultimate Contributor |
|
|
Another implementation using initializer_list:
template <typename T>
constexpr T get_i(int i, std::initializer_list<T> list)
{
return list[clamp(i, 0, list.size())];
}
This one is trictly-typed, although I couldn't check assembly with godbolt because it complains about something ...
Regards,
Novo
|
|
|
|
|
|
| Re: get_i [message #54259 is a reply to message #54258] |
Tue, 16 June 2020 21:42   |
Novo
Messages: 1431 Registered: December 2006
|
Ultimate Contributor |
|
|
mirek wrote on Tue, 16 June 2020 15:25Novo wrote on Tue, 16 June 2020 21:22Another implementation using initializer_list:
template <typename T>
constexpr T get_i(int i, std::initializer_list<T> list)
{
return list[clamp(i, 0, list.size())];
}
This one is trictly-typed, although I couldn't check assembly with godbolt because it complains about something ...
Nah, we do not want strict typing here.
Sorry, last one won't compile.
The one using variadic template is fine, although it still needs specialization for const char* ... :-/
Regards,
Novo
|
|
|
|
| Re: get_i [message #54260 is a reply to message #54256] |
Tue, 16 June 2020 22:15   |
Novo
Messages: 1431 Registered: December 2006
|
Ultimate Contributor |
|
|
mirek wrote on Tue, 16 June 2020 15:20
However, constexpr I still do not agree. Following your logic, we should add constexpr to every single function everywhere - these are as likely to have constant parameters as get_i (which has like 0.00000001% chance that first parameter will be const in real code).
You cannot add constexpr to every single function everywhere. There are restrictions ...
But, IMHO, function, which can be compiled with constexpr, should have it ...
At this time you are not using functions in compile-time context because you just cannot do that.
I personally often write code like this:
enum e {
e01 = 1,
e02 = 100,
e03 = e01 + e02
};
In case of constexpr functions I'll be able to write this:
enum e {
e01 = min(something, something_else),
e02 = max(something, something_else)
};
Another observation: template functions/methods are inline by default.
Regards,
Novo
|
|
|
|
| Re: get_i [message #54261 is a reply to message #54256] |
Wed, 17 June 2020 00:01   |
 |
mirek
Messages: 14290 Registered: November 2005
|
Ultimate Member |
|
|
mirek wrote on Tue, 16 June 2020 21:20Novo wrote on Tue, 16 June 2020 18:21Another experiment/suggestion.
I rewrote get_i using variadic template:
template <typename A, typename... T>
constexpr A get_i(int i, const A& p0, const T& ...args)
{
A list[] = {p0, args...};
int n = sizeof...(args);
return list[clamp(i, 0, n)];
}
const char* cr = get_i(1, "zero", "one", "two");
RDUMP(cr);
int ir = get_i(1, 0, 1, 2);
RDUMP(ir);
IMHO, my implementation is much shorter and it will compile faster.
IMHO, macroses __List and __Expand are not needed anymore ...
Yes, you are right about this, I have used old tricks mostly out of habit. I guess I will have to rewrite it all now 
Do you see any problems?
template <class T, class V>
constexpr V decode(const T& sel, const V& def)
{
return def;
}
template <class T>
constexpr const char *decode(const T& sel, const char *def)
{
return def;
}
template <class T, class K, class V, typename... L>
constexpr V decode(const T& sel, const K& k, const V& v, const L& ...args)
{
return sel == k ? v : (V)decode(sel, args...);
}
template <class T, class K, typename... L>
constexpr const char *decode(const T& sel, const K& k, const char *v, const L& ...args)
{
return sel == k ? v : decode(sel, args...);
}
template <class T, class K>
constexpr int findarg(const T& x, const K& k)
{
return x == k ? 0 : -1;
}
template <class T, class K, typename... L>
constexpr int findarg(const T& sel, const K& k, const L& ...args)
{
if(sel == k)
return 0;
int q = findarg(sel, args...);
return q >= 0 ? q + 1 : -1;
}
|
|
|
|
| Re: get_i [message #54262 is a reply to message #54259] |
Wed, 17 June 2020 06:39   |
Novo
Messages: 1431 Registered: December 2006
|
Ultimate Contributor |
|
|
Novo wrote on Tue, 16 June 2020 15:42mirek wrote on Tue, 16 June 2020 15:25Novo wrote on Tue, 16 June 2020 21:22Another implementation using initializer_list:
template <typename T>
constexpr T get_i(int i, std::initializer_list<T> list)
{
return list[clamp(i, 0, list.size())];
}
This one is trictly-typed, although I couldn't check assembly with godbolt because it complains about something ...
Nah, we do not want strict typing here.
Sorry, last one won't compile.
The one using variadic template is fine, although it still needs specialization for const char* ... :-/
Fixed version. No performance degradation.
template <typename T>
constexpr T get_i2(int i, const std::initializer_list<T>& list)
{
const int n = list.size();
return *(list.begin() + clamp(i, 0, n));
}
const char* c = get_i2(1, {"zero", "one", "two"});
Assembler:
.L.str:
.asciz "one"
c:
.quad .L.str
Regards,
Novo
|
|
|
|
| Re: get_i [message #54263 is a reply to message #54261] |
Wed, 17 June 2020 07:01   |
Novo
Messages: 1431 Registered: December 2006
|
Ultimate Contributor |
|
|
mirek wrote on Tue, 16 June 2020 18:01
Do you see any problems?
int ind = findarg(1, "0", 1.5, 2, 3);
RDUMP(ind);
ind = 1 in my case ...
Regards,
Novo
|
|
|
|
| Re: get_i [message #54264 is a reply to message #54263] |
Wed, 17 June 2020 07:09   |
Novo
Messages: 1431 Registered: December 2006
|
Ultimate Contributor |
|
|
Novo wrote on Wed, 17 June 2020 01:01mirek wrote on Tue, 16 June 2020 18:01
Do you see any problems?
int ind = findarg(1, "0", 1.5, 2, 3);
RDUMP(ind);
ind = 1 in my case ...
Sorry, I was testing against current implementation in U++ again.
New implementation is fine.
Regards,
Novo
|
|
|
|
| Re: get_i [message #54265 is a reply to message #54264] |
Wed, 17 June 2020 07:23   |
Novo
Messages: 1431 Registered: December 2006
|
Ultimate Contributor |
|
|
Well, it is "fine" because "findarg(1, "0", 1.5, 2, 3)" won't compile ...
But if you need a heterogeneous set of arguments, then you need to implement it differently ...
Regards,
Novo
|
|
|
|
| Re: get_i [message #54266 is a reply to message #54265] |
Wed, 17 June 2020 07:50   |
Novo
Messages: 1431 Registered: December 2006
|
Ultimate Contributor |
|
|
Code below works for all data types, including const char*.
template <class T, class V>
constexpr auto decode(const T& sel, const V& def)
{
return def;
}
template <class T, class K, class V, typename... L>
constexpr auto decode(const T& sel, const K& k, const V& v, const L& ...args)
{
return sel == k ? v : decode(sel, args...);
}
Regards,
Novo
|
|
|
|
| Re: get_i [message #54267 is a reply to message #54265] |
Wed, 17 June 2020 09:35   |
 |
mirek
Messages: 14290 Registered: November 2005
|
Ultimate Member |
|
|
Novo wrote on Wed, 17 June 2020 07:23Well, it is "fine" because "findarg(1, "0", 1.5, 2, 3)" won't compile ...
But if you need a heterogeneous set of arguments, then you need to implement it differently ...
Well, I really think above one should not compile... Heterogenous yes, but arguments must be comparable...
Mirek
|
|
|
|
| Re: get_i [message #54268 is a reply to message #54267] |
Wed, 17 June 2020 13:03   |
 |
mirek
Messages: 14290 Registered: November 2005
|
Ultimate Member |
|
|
So I started looking into eliminating all instances of Expand macro usage and identified that following helpers could be quite useful:
template <class I, class V>
void iter_set(I t, V&& v)
{
*t++ = v;
}
template <class I, class V, typename... Args>
void iter_set(I t, V&& v, Args&& ...args)
{
*t++ = v;
iter_set(t, args...);
}
template <class C, typename... Args>
C gather(Args&& ...args)
{
C x;
x.SetCount(sizeof...(args));
iter_set(x.begin(), args...);
return x;
}
template <class I, class V>
void iter_get(I s, V& v)
{
v = *s++;
}
template <class I, class V, typename... Args>
void iter_get(I s, V& v, Args& ...args)
{
v = *s++;
iter_get(s, args...);
}
template <class C, typename... Args>
int scatter(int n, const C& c, Args& ...args)
{
if(n < sizeof...(args))
return 0;
iter_get(c.begin(), args...);
return sizeof...(args);
}
template <class C, typename... Args>
int scatter(const C& c, Args& ...args)
{
return scatter(c.GetCount(), c, args...);
}
Usage example:
template <typename... Args>
String Format(const char *fmt, const Args& ...args)
{
return Format(fmt, gather<Vector<Value>>(args...));
}
But I guess this would work even better if containers interface was amended to be more "std" (Vector::Vector(int count), size() synonyme for GetCount), so I guess that needs a bit more work...
Mirek
|
|
|
|
| Re: get_i [message #54271 is a reply to message #54268] |
Wed, 17 June 2020 19:00   |
Novo
Messages: 1431 Registered: December 2006
|
Ultimate Contributor |
|
|
A fix:
template <class I, class V>
void iter_set(I t, V&& v)
{
*t++ = std::forward<V>(v);
}
template <class I, class V, typename... Args>
void iter_set(I t, V&& v, Args&& ...args)
{
*t++ = std::forward<V>(v);
iter_set(t, std::forward<Args>(args)...);
}
template <class C, typename... Args>
C gather(Args&& ...args)
{
C x;
x.SetCount(sizeof...(args));
iter_set(x.begin(), std::forward<Args>(args)...);
return x;
}
template <typename... Args>
String Format(const char *fmt, Args&& ...args)
{
return Format(fmt, gather<Vector<Value>>(std::forward<Args>(args)...));
}
Regards,
Novo
|
|
|
|
| Re: get_i [message #54272 is a reply to message #54271] |
Wed, 17 June 2020 19:14   |
Novo
Messages: 1431 Registered: December 2006
|
Ultimate Contributor |
|
|
Another fix to avoid extra-copying ...
template <class I, class V>
void iter_set(I& t, V&& v)
{
*t = std::forward<V>(v);
}
template <class I, class V, typename... Args>
void iter_set(I& t, V&& v, Args&& ...args)
{
*t++ = std::forward<V>(v);
iter_set(t, std::forward<Args>(args)...);
}
template <class C, typename... Args>
C gather(Args&& ...args)
{
C x;
x.SetCount(sizeof...(args));
auto iter = x.Begin();
iter_set(iter, std::forward<Args>(args)...);
return x;
}
Regards,
Novo
|
|
|
|
| Re: get_i [message #54273 is a reply to message #54272] |
Wed, 17 June 2020 21:03   |
 |
mirek
Messages: 14290 Registered: November 2005
|
Ultimate Member |
|
|
Novo wrote on Wed, 17 June 2020 19:14Another fix to avoid extra-copying ...
template <class I, class V>
void iter_set(I& t, V&& v)
{
*t = std::forward<V>(v);
}
template <class I, class V, typename... Args>
void iter_set(I& t, V&& v, Args&& ...args)
{
*t++ = std::forward<V>(v);
iter_set(t, std::forward<Args>(args)...);
}
template <class C, typename... Args>
C gather(Args&& ...args)
{
C x;
x.SetCount(sizeof...(args));
auto iter = x.Begin();
iter_set(iter, std::forward<Args>(args)...);
return x;
}
Thanks. Trunk version are a bit different now, can you review?
|
|
|
|
|
|
| Re: get_i [message #54275 is a reply to message #54274] |
Wed, 17 June 2020 23:59   |
Novo
Messages: 1431 Registered: December 2006
|
Ultimate Contributor |
|
|
A more generic version of
template <typename... T>
constexpr const char *get_i(int i, const char* p0, const T& ...args)
{
const char *list[] = { p0, args... };
return list[clamp(i, 0, (int)sizeof...(args))];
}
template <typename A, typename... T>
constexpr A* get_i(int i, A* p0, const T& ...args)
{
A* list[] = { p0, args... };
return list[clamp(i, 0, (int)sizeof...(args))];
}
Example:
const char* cr = get_i(1, "0", "11", "222");
RLOG(cr);
cr = get_i(1, "0", String("11"), "222");
RLOG(cr);
const wchar _0[] = {0};
const wchar _3[] = {2, 2, 2};
const wchar* wcr = get_i(1, _0, WString("11"), _3);
RLOG(wcr);
Regards,
Novo
[Updated on: Thu, 18 June 2020 00:00] Report message to a moderator
|
|
|
|
|
|
| Re: get_i [message #54282 is a reply to message #54279] |
Thu, 18 June 2020 23:56   |
Novo
Messages: 1431 Registered: December 2006
|
Ultimate Contributor |
|
|
mirek wrote on Thu, 18 June 2020 02:39Thanks, applied...
Mirek
No problem.
There is still a bug with
template <typename P, typename... T>
constexpr const P *get_i(int i, const P* p0, const T& ...args)
{
const char *list[] = { p0, args... };
return list[clamp(i, 0, (int)sizeof...(args))];
}
It has to look like below.
template <typename P, typename... T>
constexpr const P *get_i(int i, const P* p0, const T& ...args)
{
const P *list[] = { p0, args... };
return list[clamp(i, 0, (int)sizeof...(args))];
}
Regards,
Novo
|
|
|
|
| Re: get_i [message #54348 is a reply to message #54247] |
Mon, 29 June 2020 19:23   |
 |
mirek
Messages: 14290 Registered: November 2005
|
Ultimate Member |
|
|
I have just found that this fails with Visual C++ compiler:
String n = " 2";
ASSERT(decode(4, 1, "one", 2, "two", 3, "three", "unknown" + n) == String("unknown 2"));
The problem is that temporaty object gets destroyed too early...
I _believe_ this is a compiler error. In any case, it is pretty bad.
EDIT: Not a compiler bug. The problem is in your decode. Fixed it with
template <class T, class V>
constexpr const V& decode(const T& sel, const V& def)
{
return def;
}
template <class T, class K, class V, typename... L>
constexpr const V& decode(const T& sel, const K& k, const V& v, const L& ...args)
{
return sel == k ? v : decode(sel, args...);
}
template <class T>
constexpr const char *decode(const T& sel, const char *def)
{
return def;
}
template <class T, class K, typename... L>
constexpr const char *decode(const T& sel, const K& k, const char *v, const L& ...args)
{
return sel == k ? v : (const char *)decode(sel, args...);
}
[Updated on: Mon, 29 June 2020 23:08] Report message to a moderator
|
|
|
|
| Re: get_i [message #54350 is a reply to message #54247] |
Thu, 02 July 2020 22:08   |
Novo
Messages: 1431 Registered: December 2006
|
Ultimate Contributor |
|
|
Sorry for the late response.
My code is correct. Temporary String lives only during function call. This is how C++ works.
Return type is a value, not a reference. So, no temporaries ...
"The type of the ternary ?: expression is the common type of its second and third argument. If both types are the same, you get a reference back. If they are convertable to each other, one gets chosen and the other gets converted (promoted in this case). Since you can't return an lvalue reference to a temporary (the converted / promoted variable), its type is a value type."
Basically, the ternary ?: is needed to convert "const char[N]" and "const char[M]" to "const char*".
On the other side, templates is a complicated thing.
If you have a non-template version of "decode" declared before template instantiation point, compiler will choose it ...
Also MSVC is very well known for broken "two-phase name lookup". Even till these days, I believe ...
IMHO, a safer version would look like this:
namespace details {
template <class T, class V>
constexpr auto decode(const T& sel, const V& def)
{
return def;
}
template <class T, class K, class V, typename... L>
constexpr auto decode(const T& sel, const K& k, const V& v, const L& ...args)
{
return sel == k ? v : details::decode(sel, args...);
}
}
template <class T, class K, class V, typename... L>
constexpr auto decode(const T& sel, const K& k, const V& v, const L& ...args)
{
return details::decode(sel, k, v, args...);
}
Regards,
Novo
|
|
|
|
| Re: get_i [message #54353 is a reply to message #54350] |
Fri, 03 July 2020 09:38   |
 |
mirek
Messages: 14290 Registered: November 2005
|
Ultimate Member |
|
|
Novo wrote on Thu, 02 July 2020 22:08Sorry for the late response.
My code is correct. Temporary String lives only during function call. This is how C++ works.
I would not be fixing if it was correct. Whole thing was actual error in actual application.
Indeed, temp string lives only during function call. What happened here is that in some circustances when mixing String and const char * parameters, const char * gets converted to String temporary, then back to const char *, then temporary is detroyed and dangling const char * returned.
Quote:
Return type is a value, not a reference. So, no temporaries ...
And that is exactly the problem. That return value is temporary one level up and gets converted to const char *....
It is very tricky indeed. Actually the version posted here was not final, it needed more fixes for other situations (namely enums). Hopefully trunk version is now ok. Full test is in "autotest/decode". Also the error only appears with MSC, but I have checked, the problem is not in the compiler.
[Updated on: Fri, 03 July 2020 10:10] Report message to a moderator
|
|
|
|
| Re: get_i [message #54362 is a reply to message #54353] |
Fri, 03 July 2020 18:53   |
Novo
Messages: 1431 Registered: December 2006
|
Ultimate Contributor |
|
|
I guess that the problem is that in case of MSVC common type of const char* and String is const char*, and in case of Clang it is String.
Regards,
Novo
|
|
|
|
|
|
Goto Forum:
Current Time: Sat Apr 25 04:09:19 GMT+2 2026
Total time taken to generate the page: 0.01074 seconds
|