I asked a question about it and didn't get a really clear answer, but after reading this article I started preferring const char[]
to const char*
.
I've come upon a difficulty when initializing with a ternary. Given const bool bar
, I've tried:
const char foo[] = bar ? "lorem" : "ipsum"
which gives me the error:error: initializer fails to determine size of
foo
const char foo[] = bar ? { 'l', 'o', 'r', 'e', 'm', '\0' } : { 'i', 'p', 's', 'u', 'm', '\0' }
which gives me the error:error: expected primary-expression before
{
token
Is there a way to initialize a const char []
with a ternary, or do I have to switch to const char*
here?
Since string literals are lvalues, you can take const references of them, which can be used in a ternary.
// You need to manually specify the size
const char (&foo)[6] = bar ? "lorem" : "ipsum";
// Or (In C++11)
auto foo = bar ? "lorem" : "ipsum";
auto
would behave exactly the same (Except that you would have to specify the size).
If you want to do it for different length strings, unfortunately "bool ? const char[x] : const char[y]
" would only be an array type if they have the same size (Otherwise they would both decay into pointers, and the expression would be of type const char*
). To remedy this, you would have to manually pad the string with \0
characters (And now you can't do sizeof(foo) - 1
to get the size, you would have to do strlen(foo)
).
For example, instead of:
auto foo = bar ? "abc" : "defg"; // foo is a const char*
You would have to do:
auto foo = bar ? "abc\0" : "defg"; // foo is const char(&)[5]
// Note that if `bar` is true, foo is `{'a', 'b', 'c', '\0', '\0'}`
Before you have to do this, consider that if you set your variables as const char * const
, your compiler will more than likely optimise them to be exactly the same as if they were const char[]
, and probably also the same if they were only const char *
(no const at the end) if you don't change the value.
To not accidentally get a pointer, and fail fast if you do, I would use a helper function:
#include <cstddef>
template<std::size_t size, std::size_t other_size = size>
constexpr auto conditional(bool condition, const char(&true_case)[size], const char(&false_case)[other_size]) noexcept -> const char(&)[size] {
static_assert(size == other_size, "Cannot have a c-string conditional with c-strings of different sizes");
return condition ? true_case : false_case;
}
// Usage:
auto foo = conditional(bar, "lorem", "ipsum");
If bar
is a compile time constant, you can change the type of foo
depending on the value of bar
. For example:
#include <cstddef>
template<bool condition, std::size_t true_size, std::size_t false_size>
constexpr auto conditional(const char(&true_case)[true_size], const char(&false_case)[false_size]) -> typename std::enable_if<condition, const char(&)[true_size]>::type {
return true_case;
}
template<bool condition, std::size_t true_size, std::size_t false_size>
constexpr auto conditional(const char(&true_case)[true_size], const char(&false_case)[false_size]) -> typename std::enable_if<!condition, const char(&)[false_size]>::type {
return false_case;
}
// Or with C++17 constexpr if
template<bool condition, std::size_t true_size, std::size_t false_size>
constexpr auto conditional(const char(&true_case)[true_size], const char(&false_case)[false_size]) -> const char(&)[condition ? true_size : false_size] {
if constexpr (condition) {
return true_case;
} else {
return false_case;
}
}
// Usage:
auto foo = conditional<bar>("dolor", "sit");