Home > Software design >  Target-typing in modern C
Target-typing in modern C

Time:01-19

When I say target-typing I mean using the type of the receiver variable or parameter as information to infer parts of the code I'm assigning to it. Like for example, in C# you'd write something like this to pass a nullable value or a null (empty) if necessary:

void f(int? i) {}
void caller(bool b) => 
    f(b ? 5 : null);  // value is bound to an int? parameter so all information 
                      // to build this object is already in code

The best I could come up with in C is something like this:

void f(const optional<int>& i) {}
void caller(bool b)
{
    f(b ? make_optional(5) : optional<int>());
}

And it works, but it requires me to write optional twice and provide the optional type once, even though all the information should already be in code. This is, in my humble opinion, what type inference should do for you without having to repeat yourself multiple times per line. And because the following does work:

optional<int> i;
i = {};  // empty
i = {5}; // a value

I'd have thought that the next logical step would also work:

void f(const optional<int>& i) {}
void caller(bool b)
{
    f(b ? {5} : {});  // doesn't compile
}

So overall from various experiments it seems target-typing works when directly assigning a value to a variable, but not as part of a ternary conditional expression bound to a parameter? Am I understanding this correctly? There is no limit on the C version, I've been writing this in MS C 20 for instance.

And a related question, though not the main focus of my question, is there a better way of writing an optional<> initialization that can either have a value or not based on a boolean?

CodePudding user response:

In C , expressions have types.

{} is not, however, an expression. There are a handful of restricted contexts where {} could be used when you'd expect an expression should go there. This doesn't make {} into an expression.

?: is an operation, and its arguments must have types. The type of ?: is derived from the type of its 2 arguments (the rules are complex, but it tries to find a common type).

b ? std::optional{5} : std::nullopt;

is similar to what you want. Here, the fact that std::nullopt can convert to a std::optional<int> is used to deduce the type of ?: expression.

You can also write:

template<class T>
std::optional<std::decay_t<T>> maybe( bool b, T&& t ) {
  if (!b) return std::nullopt;
  return std::forward<T>(t);
}

which lets you write

maybe( b, 5 )

for your case.

Fancier things can be done, where you use template<class T> operator T() on a type to deduce the type you are returning into.

But, in general, the C type system goes one way.

CodePudding user response:

@Yakk-AdamNevraumont already gave the fully fledged answer, so I'll just give the short one, your code can work if you switch the short version, ternary operator condition, with a full if statement:

void f(const optional<int>& i) {}
void caller(bool b)
{
    if (b) {
        f(5);
    }
    else {
        f({});
    }
}
  •  Tags:  
  • Related