constexpr get index of item in array - c++

I have the following code:
static const char* ITEMS[] = { "a", "b" } // too many elements for a std::array in my situation
void my_func() {
printf("a: %d", index_of(ITEMS, "a")); // 0
printf("d: %d", index_of(ITEMS, "d")); // error at compile time; "d" does not exist in ITEMS.
}
How would I define a similar index_of method? I've seen various suggestions on other SO posts, but they don't work for the following reasons:
Use std::string/Make ITEMS a constexpr
Unfortunately ImGui's Combo requires a const char** for the array type which is impossible with std::string, and similarly, as ITEMS would be a constexpr it would be of type const char* const *, which does not work.
Use std::array instead of T[]
The number of elements is too high for the compiler to handle, resulting in a compiler error.

First, you must declare the array as constexpr. Otherwise its values are not usable at compile-time:
static constexpr const char* ITEMS[] = { "a", "b" };
As a consequence the type of the elements will be const char* const. If a library function expects them to be non-const then presumably that is because the library may attempt to modify them. That is of course impossible if they are supposed to be compile-time constants.
If you are absolutely sure that the library is simply lying about needing to modify the pointer value, then you can cast const away with const_cast. However, if the library then does attempt to modify the elements, then your program will have undefined behavior.
Then you also need C++20. Otherwise there is no way to force a compile-time error from failure of constant expression evaluation in a simple function call. Specifically you need the consteval feature.
With that:
template<typename R>
consteval auto index_of(const R& range, std::string_view needle) {
auto it = std::ranges::find(range, needle);
if(it == std::ranges::end(range))
throw std::logic_error("Element not found!");
return std::ranges::distance(std::ranges::begin(range), it);
}
This works with both built-in arrays and std::array.
Then you also need to replace %d with %zu, because the iterator difference type returned by this index_of is std::size_t for built-in arrays. %d is for the wrong type (int).

Related

C++ Template - passing const value of type T by reference

I have a function with a template parameter T and would like to pass a value of type const T by reference.
The C++ compiler throws an error, (kind of) understandably so. Hence I was wondering if there exists a way to do this in a safe and concise way?
I created a very small example that reflects the issue I am having in my project.
(in my project the issue appears in a constant member function of some class, but from my experiments the issue should be "faithfully" reflected in the example below by use of a constant variable of int instead for simplicity's sake).
I am aware that I could theoretically use a separate template parameter "cT", but that would be horribly unsafe, as the caller of this function need not pass an object of "const T" as second argument ...
I also understand that I could simply refrain from using templates at all and just specify this for every type.
I was just wondering if what I am trying to achieve below can be done with templates.
Thanks and have a nice day! :)
template<typename T>
bool ContainsElement(std::list<T>& findList, const T& elem)
{
for (auto& entry : findList)
{
if (entry == elem)
return true;
}
return false;
}
int main()
{
std::list<int*> myList;
const int testConst = 6;
auto pointerToTestConst = &testConst;
ContainsElement(myList, pointerToTestConst); // compiler screams
}
The issue is in incompatibility between pointers:
pointerToTestConst is of type const int* - non-const pointer to const integer. Therefore T=const int*
myList is of type list<int*>, deducing T=int*.
Since those types are not the same, compilation fails. Rightfully so, because elem would allow changing testConst if T=int*.
The issue manifests regardles you passing elem by (const) reference or value.
horribly unsafe, as the caller of this function need not pass an object of "const T" as second argument
So what? They get a compiler error about the comparison. It is no more unsafe than your code right now. I would argue that a generic ContainsElement should not care what you pass to is as long as it compares equal to some element in the list, it is a match.
Of course STL already offers this for std::find and std::ranges::find which also does not care and it is not called unsafe because of it.
To "normalize" the type for pointers and non-pointers, std::conditional may be used, finally your template still defines single T parameter:
#include <type_traits>
template<typename T>
bool ContainsElement(std::list<T>& findList,
const std::conditional_t<
std::is_pointer<T>::value,
std::add_pointer_t<
std::add_const_t<
std::remove_pointer_t<T>
>
>,
std::add_const_t<T>
>& elem)
{
for (auto& entry : findList)
{
if (entry == elem)
return true;
}
return false;
}
Above example uses helper types with postfix _t introduced in C++14, for C++11, use std::conditional::type.
It works for std::list<int> and std::list<int*>.
Of course in case of std::list<int*> it will check whether list contains exactly the same pointer address, not the pointed value.

In c++ how to check a pointer lies within a range?

Intuitively to check whecker pointer p lies in [a,b) one will do
a<=p && p<b
However, comparing pointers from two arrays results in unspecified behavior and thus we cannot safely say p is in [a,b) from this comparison.
Is there any way one can check for this with certainty?
(It would be better if it can be done for std::vector<T>::const_iterator, but I don't think it's feasible.)
Here's a partial solution. You can leverage the fact that the comparison would invoke unspecified behavior, and the fact that a core-constant-expression can't perform this operation:
template<typename T>
constexpr bool check(T *p, T *a, T *b)
{
return a <= p and p < b;
}
Now this function can be used like this:
int main()
{
int arr[5];
int arr_2[5];
constexpr bool b1 = check(arr + 1, arr, arr + 3); // ok
constexpr bool b2 = check(arr_2 + 1, arr, arr + 3); // error
}
Here's a demo.
This obviously works only if the pointer values are known at compile time. At run-time, there is no efficient way of doing this check.
The solution for pointers is to use the comparison objects defined in <functional>, like less/less_equal, etc.
From §20.8.5/8 of the c++17 standard1:
For templates greater, less, greater_equal, and less_equal, the specializations for any pointer type yield a total order, even if the built-in operators <, >, <=, >= do not.
So the solution for pointers would be:
template<typename T>
bool check(T *p, T *a, T *b)
{
return std::less_equal<T*>{}(a,p) && std::less<T*>{}(p,b);
}
Here's a working example using pointers.
There is no such strict guarantee for iterators; however this can be worked around in c++20, since it provides std::to_address which can convert pointable objects to pointers. Note, however, that the behavior of doing this for the purpose of comparisons is only really well defined for contiguous iterators.
Since we know that std::vector iterators cover a contiguous range, we can use this to retrieve the underlying pointer (note: not dereference it, as this would be undefined behavior for the past-the-end pointer).
So for a std::vector<T>::iterator, a solution might look like:
template <typename T>
bool check(const std::vector<T>::const_iterator p, std;:vector<T>::const_iterator a, std::vector<T>::const_iterator b)
{
// Delegate to the pointer check version defined above, for brevity
return check(std::to_address(p), std::to_address(a), std::to_address(b));
}
Here's a working example using iterators.
1 This same note exists all the way back to c++11 under §23.14.7/2, with similar wording.
If I understand you correctly, you want to check if vector iterator is between two other vector iterators.
Then you may use std::distance to compute distance between vector.begin and a, p and b and then simply compare itegers you get from distance return value.
std::distance(first, last) from C++17 can be used for both, but result is undefined if last is unreachable from first (e.g. different range or invalid iterator)

In C++, why overload a function on a `const char array` and a private struct wrapping a `const char*`?

I recently ran into a fascinating class in the ENTT library. This class is used to calculate hashes for strings like so:
std::uint32_t hashVal = hashed_string::to_value("ABC");
hashed_string hs{"ABC"};
std::uint32_t hashVal2 = hs.value();
While looking at the implementation of this class I noticed that the none of the constructors or hashed_string::to_value member functions take a const char* directly. Instead, they take a simple struct called const_wrapper. Below is a simplified view of the class' implementation to illustrate this:
/*
A hashed string is a compile-time tool that allows users to use
human-readable identifers in the codebase while using their numeric
counterparts at runtime
*/
class hashed_string
{
private:
struct const_wrapper
{
// non-explicit constructor on purpose
constexpr const_wrapper(const char *curr) noexcept: str{curr} {}
const char *str;
};
inline static constexpr std::uint32_t calculateHash(const char* curr) noexcept
{
// ...
}
public:
/*
Returns directly the numeric representation of a string.
Forcing template resolution avoids implicit conversions. An
human-readable identifier can be anything but a plain, old bunch of
characters.
Example of use:
const auto value = hashed_string::to_value("my.png");
*/
template<std::size_t N>
inline static constexpr std::uint32_t to_value(const char (&str)[N]) noexcept
{
return calculateHash(str);
}
/*
Returns directly the numeric representation of a string.
wrapper parameter helps achieving the purpose by relying on overloading.
*/
inline static std::uint32_t to_value(const_wrapper wrapper) noexcept
{
return calculateHash(wrapper.str);
}
/*
Constructs a hashed string from an array of const chars.
Forcing template resolution avoids implicit conversions. An
human-readable identifier can be anything but a plain, old bunch of
characters.
Example of use:
hashed_string hs{"my.png"};
*/
template<std::size_t N>
constexpr hashed_string(const char (&curr)[N]) noexcept
: str{curr}, hash{calculateHash(curr)}
{}
/*
Explicit constructor on purpose to avoid constructing a hashed
string directly from a `const char *`.
wrapper parameter helps achieving the purpose by relying on overloading.
*/
explicit constexpr hashed_string(const_wrapper wrapper) noexcept
: str{wrapper.str}, hash{calculateHash(wrapper.str)}
{}
//...
private:
const char *str;
std::uint32_t hash;
};
Unfortunately I fail to see the purpose of the const_wrapper struct. Does it have something to do with the comment at the top, which states "A hashed string is a compile-time tool..."?
I am also unsure about what the comments that appears above the template functions mean, which state "Forcing template resolution avoids implicit conversions." Is anyone able to explain this?
Finally, it is interesting to note how this class is used by another class that maintains an std::unordered_map of the following type: std::unordered_map<hashed_string, Resource>
This other class offers a member function to add resources to the map using strings like keys. A simplified view of its implementation looks like this:
bool addResource(hashed_string id, Resource res)
{
// ...
resourceMap[id] = res;
// ...
}
My question here is: what is the advantage of using hashed_strings as the keys to our map instead of std::strings? Is it more efficient to work with numeric types like hashed_strings?
Thank you for any information. Studying this class has helped me learn so much.
The author is trying to help you avoid accidental performance problems that happen when you repeatedly hash strings. Since hashing strings is expensive, you probably want to do it once and cache it somewhere. If they have an implicit constructor, you could hash the same string repeatedly without knowing or intending to do so.
So the library provides implicit construction for string literals, which can be computed at compile-time via constexpr but explicit construction for const char* in general since those can't generally be done at compile-time and you want to avoid doing it repeatedly or accidentally.
Consider:
void consume( hashed_string );
int main()
{
const char* const s = "abc";
const auto hs1 = hashed_string{"my.png"}; // Ok - explicit, compile-time hashing
const auto hs2 = hashed_string{s}; // Ok - explicit, runtime hashing
consume( hs1 ); // Ok - cached value - no hashing required
consume( hs2 ); // Ok - cached value - no hashing required
consume( "my.png" ); // Ok - implicit, compile-time hashing
consume( s ); // Error! Implicit, runtime hashing disallowed!
// Potential hidden inefficiency, so library disallows it.
}
If I remove the last line, you can see how the compiler applies the implicit conversions for you at C++ Insights:
consume(hashed_string(hs1));
consume(hashed_string(hs2));
consume(hashed_string("my.png"));
But it's refusing to do so for the line consume(s) because of the implict/explicit constructors.
Note, however, this attempt at protecting the user isn't foolproof. If you declare your string as an array rather than as a pointer, you can accidentally re-hash:
const char s[100] = "abc";
consume( s ); // Compiles BUT it's doing implicit, runtime hashing. Doh.
// Decay 's' back to a pointer, and the library's guardrails return
const auto consume_decayed = []( const char* str ) { consume( str ); }
consume_decayed( s ); // Error! Implicit, runtime hashing disallowed!
This case is less common, and such arrays typically get decayed into pointers as they are passed to other functions, which would then behave as above. The library could conceivably enforce compile-time hashing for string literals with if constexpr and the like and forbid it for non-literal arrays like s above. (There's your pull request to give back to the library!) [See comments.]
To answer your final question: The reasons for doing this are to have faster performance for hash-based containers like std::unordered_map. It minimizes the number of hashes you have to do by computing the hash once and caching it inside the hashed_string. Now, a key lookup in the map just has to compare the pre-computed hash values of the keys and the lookup string.

Transform predicate returning local variable by reference

In the following code, I am trying to copy a std::vector<char>, into a std::vector<uint8_t>. To avoid compiler warning, I am doing explicit casting inside the predicate of std::transform instead of using std::copy, like this
std::vector<char> buf(10, 10);
std::vector<uint8_t> u(10);
std::transform(std::begin(buf), std::end(buf), std::begin(u),
[](uint8_t val)->decltype(*std::begin(u)){
return static_cast<decltype (*std::begin(u))> (val);
});
For this code, I get compiler warning saying that I am trying to return a local variable val by reference.
Why is that and how can it be fixed?
The compiler is right. In the return type of your lambda:
decltype(*std::begin(u))
dereferencing the iterator returned by std::vector<T>::begin() returns T& or T const& depending on the const qualification of u. Secondly, your lambda exhibits Undefined Behavior because you are returning a reference to a temporary, val.
To solve your problems, you can use std::decay_t
std::decay_t<decltype(*std::begin(u))>
Which leads us to:
std::transform(std::begin(buf), std::end(buf), std::begin(u),
[](auto val)-> std::decay_t<decltype(*std::begin(u))> {
return val;
});
------------------------------------------------------
If the types are implicitly convertible, you can simply do:
std::vector<uint8_t> u(std::begin(buf), std::end(buf));
or, to an already created u:
u.assign(std::begin(buf), std::end(buf));
On the other hand, for some other complex type, a plain loop isn't bad:
for(const auto& k : buf)
u.emplace( k.converted() ); //Assuming you must call a conversion function
What you're actually looking for is the range constructor of vector:
std::vector<char> buf(10, 10);
std::vector<uint8_t> u(buf.begin(), buf.end());
This is both more efficient and easier to read than the solution involving transform().

std::set::find vs std::find on std::set with const

I wrote a little (working) test code but I do not understand why in the test1 function I can only pass a int* const as parameter while in the test2 function I can pass a const int*. If I pass a const int* to test1, I get a discard qualifier error.
In my research, I found that both std::find and set::find have a const version so I can't see why they behave differently. I also tried with boost::container::flat_set instead of a std::set and I got the same result.
Could someone explain me please?
class myClass
{
public:
myClass() {};
~myClass() {};
void add(int* ref)
{
this->_ref.insert(ref);
};
bool test1(int* const ref) const
{
return ( this->_ref.find(ref) != this->_ref.end() );
}
inline
bool test2(const int* ref) const
{
return ( std::find(this->_ref.begin(), this->_ref.end(), ref) != this->_ref.end() );
}
std::set<int*> _ref;
};
int main()
{
myClass test;
test.add(new int(18));
test.add(new int(35));
test.add(new int(78));
test.add(new int(156));
std::cout<<test.test1(0)<<std::endl;
std::cout<<test.test1(*test._ref.begin())<<std::endl;
std::cout<<test.test2(0)<<std::endl;
std::cout<<test.test2(*test._ref.begin())<<std::endl;
return 0;
}
set::find() gives the answer in O(logN) while std::find() gives the answer in O(N) .
Similarly, map::find() gives the answer in O(logN) while std::find() gives the answer in O(N) .
The container std::set<int*> has only homogeneous lookup, so you can only search keys by comparing them with a value of the same type: find, count, erase. Naturally, a value of type const int* does not have the same type as int*, so your test2 code attempts to convert the former to the latter, which is not an allowed conversion.
The fact that containers could only be used in a homogeneous way like that has been a shortcoming of C++ since inception, and more egregrious examples of undesired conversions are when you have a map with std::string keys and want to look up an element with a key provided as a string literal. You always have to construct the dynamic std::string object, even though std::string provide comparisons operators with string literals.
Therefore, since C++14, you can also make a set (or map) with inhomogeneous lookup by spelling it std::set<int*, std::less<>>. With such a container, the loopup functions become templates, and you can indeed compare values of different types (leaving the conversion logic to the underlying <-operator). But note that std::less<int*> is required to provide a strict weak ordering on pointers, whereas std::less<> is not, so you may end up with undefined behaviour.