Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can I Initialize a char[] with a Ternary?

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:

  1. const char foo[] = bar ? "lorem" : "ipsum" which gives me the error:

error: initializer fails to determine size of foo

  1. 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?

like image 703
Jonathan Mee Avatar asked Dec 11 '18 16:12

Jonathan Mee


Video Answer


2 Answers

There is no way to initialize character array with ternary operator. The reason for this is that both sides of ternary operator are actually used to construct an object, and than the object is used to initialize the value. Since you can't initialize one array from another, ternary initialization for arrays doesn't work.

It would for std::arrays, though, provided you explicitly specify the type (and assuming C++17):

std::array k = b ? std::array{1, 2, 3, 4} : std::array{ 5, 6, 7 ,8};

Please note, the arrays have to be of the same size. There is no way at all to use arrays of different sizes in this context, since both sides of ternary operator have to be of the same type (and size of the array is part of it's type). In case your strings are of a different sizes, you will have to use a const char* const.

like image 198
SergeyA Avatar answered Sep 23 '22 19:09

SergeyA


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");
like image 22
Artyer Avatar answered Sep 23 '22 19:09

Artyer