0

I have a function of the following signature:

void foo(int argc, char const * const argv[]);

I would like to call it in a concise way, similar, but not necessary identical as below:

foo(3, {"one", "two", "three"});

I know that C supports compound literals just for this purpose (reference).

I also know how to solve the problem using templates (reference).

However, my problem is slightly different. The function signature is fixed - it normally takes arguments passed to main and the size of the array is not predefined. I am writing tests for this function and in my case the passed arrays are known at compile-time.

Is there a way to call foo without using temporary variables or wrapper functions like below?

char const * array[3] = {"one", "two", "three"};
foo(3, array);
template<int N>
void wrapper(char const * const (&array)[N])
{
    foo(N, array);
}
Krzysiek Karbowiak
  • 1,655
  • 1
  • 9
  • 17
  • 4
    I'd recommend changing the function to take a `std::array` or `std::vector`. That's going to make your life simpler. – Jesper Juhl Jan 19 '20 at 15:43
  • @JesperJuhl And how do you pass `argc` and `argv` to a `std::array`? You can't. I already have a wrapper that takes `std::vector`, but I would like to eliminate it if possible. – Krzysiek Karbowiak Jan 19 '20 at 15:51
  • 1
    Isn't this a case for an `initializer_list`? – alle_meije Jan 19 '20 at 16:11
  • @alle_meije Not really, at least not without another wrapper function that would forward the call to `foo`. – Krzysiek Karbowiak Jan 19 '20 at 16:57
  • 1
    @KrzysiekKarbowiak: Why wouldn't you want to use the C++ wrapper you wrote? It's a much better interface as you are not allowed to *lie* about the size of the array. The size of the array given to the underlying function will always be correct. – Nicol Bolas Jan 19 '20 at 17:12
  • 1
    Have you tried `enable_if::type{"one", "two", "three"}`? Why wouldn't it work? – Johannes Schaub - litb Jan 19 '20 at 17:13
  • In Standardization of C++11, during the final days I had some good arguments for allowing array temporaries, and together with http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_closed.html#1058 and http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#1232 , they were allowed from C++11 onwards. – Johannes Schaub - litb Jan 19 '20 at 17:18
  • GCC raises an error about "taking the adress of a temporary array", but according to http://eel.is/c++draft/conv.array, both lvalue and rvalue arrays can be implicitly converted to pointers. So I'm unsure why GCC rejects. Clang accepts! – Johannes Schaub - litb Jan 19 '20 at 17:23
  • @NicolBolas: My `foo` function is used to parse command-line arguments and as such it is called with what `main` gets. In this case there is little place for errors or lies. I am writing unit tests for `foo` and would like to use the interface directly (and make the `std::vector` overload private). I am also somewhat disappointed that I can to it in C but not in C++. – Krzysiek Karbowiak Jan 19 '20 at 17:25
  • @JohannesSchaub-litb: I also noticed this difference between GCC and Clang. Later on I will check how MSVC behaves. – Krzysiek Karbowiak Jan 19 '20 at 17:27
  • @KrzysiekKarbowiak: "*I am writing unit tests for foo and would like to use the interface directly*" If you're writing unit tests for it, why is it so important to use the interface directly? I mean, you said "there is little place for errors or lies", so you're *not* testing what happens if your sizes don't match your arrays. So, isn't it important for your unit tests to be unable to accidentally mismatch the sizes with the strings? – Nicol Bolas Jan 19 '20 at 17:30
  • @NicolBolas: In my code `foo(int, char * [])` calls `foo(std::vector)`. Both overloads are public. My unit tests call the `std::vector` overload out of convenience. It means that while they cannot mismatch the sizes with the strings, the won't detect any errors in constructing the vector in the first overload. If I can call the first overload directly and conveniently, I can make the `std::vector` overload private and get better coverage. – Krzysiek Karbowiak Jan 19 '20 at 17:39
  • Also it seems unclear what you're asking. The braced list of string literals is not a valid initializer for pointer-to-pointer, however you say you do not want to make any of the changes that would supply a valid initializer – M.M Jan 20 '20 at 02:05
  • Note that `main` expects a sentinel null pointer in `argv` (although of course it can always use `argc` for that purpose instead). – Davis Herring Jan 20 '20 at 05:38
  • @M.M: OK, I see the question is not as clear as I thought. Let me edit. – Krzysiek Karbowiak Jan 20 '20 at 07:41
  • `std::span` in C++20 would probably make it much easier – tigertang Jan 20 '20 at 11:15

2 Answers2

2

though I have no idea how compiler works, it works:

template<size_t N>
void foo(int argc, const char *(&&argv)[N]){
    int i=0;
    for( auto o: argv) {
        ++i;
        cout<<"array"<<i<<" = " << o<<"\n";
    }

    cout<< "\narray size "<<i<<"\n"<<argc<<"\n";
    return;
}

int main(){

    foo(3, {"one", "two", "three"});

}

array1 = one
array2 = two
array3 = three

array size 3
3
0

You can write a simple wrapper for arguments themselves.

Example (see below):

auto wrapper(std::initializer_list<const char*> lst) {
    return lst.begin();
}

foo(3, wrapper({"one", "two", "three"}));

This solution requires that initializer_list be destroyed when foo() returns. The moment when it is destroy is implementation-defined:

It is implementation-defined whether the lifetime of a parameter ends when the function in which it is defined returns or at the end of the enclosing full-expression.

Thanks NathanOliver for pointing that out.


Edit. After the discussion that followed this question, it seems that the code above can easily be fixed by passing the initializer_list by reference, not by value, i.e.:

auto wrapper(const std::initializer_list<const char*>& lst) {
    return lst.begin();
}

foo(3, wrapper({"one", "two", "three"}));

Now the temporary initializer_list is guaranteed to exist until foo() returns:

Temporary objects are destroyed as the last step in evaluating the full-expression that (lexically) contains the point where they were created.

Thanks cdhowie for clarifications.

Evg
  • 25,259
  • 5
  • 41
  • 83