6

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?

Jonathan Mee
  • 37,899
  • 23
  • 129
  • 288

3 Answers3

2

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.

SergeyA
  • 61,605
  • 5
  • 78
  • 137
  • @NathanOliver same type of course. This is required for ternary operator - left hand side and right hand side will have to be of the same size. This is a good point indeed. – SergeyA Dec 11 '18 at 16:48
  • Unfortunately my case deals with 2 different length literal c-strings. So this may be helpful in a different case, but in mine I think I just need to use a `const char*`. If no one gives a better answer though, I'll accept this tomorrow. – Jonathan Mee Dec 11 '18 at 17:04
1

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");
Artyer
  • 31,034
  • 3
  • 47
  • 75
1

As long as the two string literals have the same size, the result of the ternary operator refers to either of the string literals, and this result has array type:

auto& x = true?"1234":"1234";
static_assert(is_same_v<decltype(x),const char (&)[5]>);

Once the result of the ternary operator established, the usual language rule applies:

  • c-array cannot be copied

    const char y[5] = x; //error
    
  • the size of a c-array can only be deduced from an initializer-list or for char array when the initializer is a string literal:

    const char z[] = {'a','b'};
    const char c[] = "abcd";
    //no other way to deduce the size
    
Oliv
  • 17,610
  • 1
  • 29
  • 72