2

I want to call a function that puts up a menu with something like this :

Mode_Menu("Item 1", "Item A1c", "Item Fred", ..... "Item xxx") where n could be any reasonable number and each item can be of random length.

I've tried the following (ignore the numbers in Offer_Mode - that's just the Y coordinate on the LCD)

void Mode_Menu(char *m1, ...)
    {
        va_list argp;
        va_start(argp, *m1);
        Offer_Mode(2, m1++);
        Offer_Mode(33, m1++);
        Offer_Mode(64, *m1++;
        Offer_Mode(97, *m1++);
        Offer_Mode(130, *m1++);        
    }

But what I get is

Item 1
tem 1
em 1
m 1
 1

i.e. the pointer is moving along the first element and never even sees the second element in the function call.

I've tried all the incantations I can think of to step along to the following elements using things like *m1[] or m1[2] in the function definition and in va_start but everything I try throws up an errors.

Can somebody point me to the correct way to do this please?

(and I have searched extensively first so please don't mark as duplicate. There's lot of examples using integers but none that I can find using char arrays).

roschach
  • 8,390
  • 14
  • 74
  • 124
Mike Bryant
  • 310
  • 2
  • 12
  • Why wouldn't you pass a `std::vector` or similarly `std::array` instead? – lubgr Mar 13 '19 at 08:48
  • 1
    In C++, I would prefer variadic templates or, may be, providing arguments in a container (like e.g. a `std::vector`). If that's not an option at all, then I would use [`va_arg`](https://en.cppreference.com/w/cpp/utility/variadic/va_arg) to access arguments. And, please, don't forget the `va_end()`. Processing variadic arguments in va_ macros can be very platform dependent and fragile if not used with care. – Scheff's Cat Mar 13 '19 at 08:48
  • Currently, you simply don't use the variadic arguments at all... You'll need [va_arg](https://en.cppreference.com/w/cpp/utility/variadic/va_arg) for (instead of incrementing first pointer argument). Be aware that you need to find a way to get the end of the arguments list yourself. You should avoid (classic) variadic functions (as @Scheff recommended already), though, they are pretty unsafe. – Aconcagua Mar 13 '19 at 08:53
  • A container isn't an option as the menu line needs to be edited directly by people who aren't C++ experts (aka musicians). I need to isolate them from all the complexities of the laguage. But yes, it was varg I was missing. Thanks to Scheff and Aconcagua – Mike Bryant Mar 13 '19 at 08:59

4 Answers4

2

The C interface for vardiac templates makes it:

void Mode_Menu(char *arg, ...)
    {
        va_list argp;
        va_start(argp, &arg);
        Offer_Mode(2, arg);
        arg=va_arg(argp, char*)
        Offer_Mode(33, arg);
        arg=va_arg(argp, char*)
        Offer_Mode(64, arg);
        ....
        va_end(argp);
    }

EDIT: The actual code should have a way to find out the end of the parameters. It can be by an integer first argument, from string information (like printf format, or by pattern matching in the strings), or by a nullptr sentinel (last parameter).

The C++ way (with SFINAE to check type correctness)

    template <typename ...T>
    std::enable_if_t<(std::is_same_v<T, char>&&...)>
    Mode_Menu(const T*... args)
    {
        const char * texts[] = { args... };

        Offer_Mode(2, texts[0]);

        Offer_Mode(33, texts[1]);

        Offer_Mode(64, texts[2]);
        ....
    }

EDIT 2: The template variant already contains size information, both in sizeof...(T) and std::size(texts). Due to that, unlike with the C variant, there is no need to work hard to detect the last string.

Michael Veksler
  • 8,217
  • 1
  • 20
  • 33
  • How is it possible to check the size of the `texts` array? In your example, if there is only one argument, you're going to have an error at `texts[1]`. – Pierre Mar 13 '19 at 09:24
  • @Pierre there isn't one argument. It is a parameter pack expansion, with as much parameters as there are in the input. You can check their number at compile time with sizeof...(T), or std:size(texts) – Michael Veksler Mar 13 '19 at 09:36
  • See https://en.cppreference.com/w/cpp/language/parameter_pack#Brace-enclosed_initializers – Michael Veksler Mar 13 '19 at 09:49
  • Variadic function variant: Should be mentioned that we yet need some way to find the end of the arguments list. My personal variant would be terminating the arguments with a null pointer as sentinel... – Aconcagua Mar 13 '19 at 19:36
  • @Aconcagua for the C variant, I fully agree but it wasn't part of the question – Michael Veksler Mar 13 '19 at 19:37
  • It wasn't part of the question most likely because OP wasn't aware of the problem, so we'd let her/him blindly run into the next trouble if we keep quiet about... – Aconcagua Mar 13 '19 at 19:43
1

I repeat what I already said in comment:

In C++, I would prefer variadic templates or, may be, providing arguments in a container (like e.g. a std::vector). If that's not an option at all, then I would use va_arg to access arguments. And, please, don't forget the va_end(). Processing variadic arguments in va_ macros can be very platform dependent and fragile if not used with care.

However, (just in remember of my own tries to resemble printf() in the far past) a sample:

#include <iostream>
#include <cstdarg>

void printMenu(int len, ...)
{
  va_list argp;
  va_start(argp, len);
  for (int i = 0; i < len; ++i) {
    const char *item = va_arg(argp, const char*);
    std::cout << '[' << (i + 1) << "]: " << item << '\n';
  }
  va_end(argp);
}

int main()
{
  printMenu(3, "Item 1", "Item A1c", "Item Fred");
  return 0;
}

Output:

[1]: Item 1
[2]: Item A1c
[3]: Item Fred

Live Demo on coliru

Notes:

  1. One difficulty is to recognize the number of variadic arguments correctly. I used a count for this. Another often used option is to remark the end (e.g. with a nullptr argument). Concerning printf(), the number of expected arguments depends directly from the contents of formatter argument. (Hence, the printf() are considered as fragile and unsafe.)

  2. There are only certain types which can be expected for arguments. More about this here: Variadic arguments - Default conversions

Scheff's Cat
  • 19,528
  • 6
  • 28
  • 56
1

You can solve specifying the number of additional arguments you are passing.

Each additional argument must be "extracted" from the list.

I used cout because I do not know what Offer_mode does.

#include<iostream>
#include <stdarg.h>
void Mode_Menu(char *m1, unsigned int count,...);

int main(int argc, char* argv[])
{
   Mode_Menu("Item 1", 3,"Item A1c", "Item Fred", "Item xxx");
  return 0;
}


void Mode_Menu(char *m1, unsigned int count, ...)
{
  va_list ap;
  va_start(ap, count); /* Requires the last fixed parameter (to get the address) */
  for (int j = 0; j < count; j++)
  {
    std::cout<<va_arg(ap, char*)<<std::endl; //ap automatically incremented
  }
  va_end(ap);
}
roschach
  • 8,390
  • 14
  • 74
  • 124
  • 1
    You forgot to use `va_end`. Why is it important: https://stackoverflow.com/questions/587128/what-exactly-is-va-end-for-is-it-always-necessary-to-call-it – Pierre Mar 13 '19 at 09:29
0

With the question and answer, I work out something as below

// gcc -std=c99  vardiac.c -o vardiac -Wall

#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <stdlib.h>

char * formatStr(char *dest,int narg, ...) {
    *dest='\0';
    va_list pargs;
    va_start(pargs, narg);
    for(int i=0;i<narg;i++) {
        char *str = va_arg(pargs,char *);
        strcat(dest,str);
        strcat(dest," ");
    }
    va_end(pargs);
    printf("Inside function: %s\n",dest);
    return dest;
}

int main() {
    char *t_str;
    int narg;
    char first[] = "First";
    char second[] = "Second";
    char third[] = "Third";
    t_str = (char *) malloc( ( strlen(first) + strlen(second) + strlen(third) + 1 ) * sizeof(char) );
    narg = 3;
    formatStr(t_str,narg,first,second,third);
    printf("Inside main: %s\n",t_str);
    narg = 2;
    formatStr(t_str,narg,first,second);
    printf("Inside main: %s\n",t_str);
    narg = 1;
    formatStr(t_str,narg,first);
    printf("Inside main: %s\n",t_str);
    free(t_str);
}
Justin
  • 11
  • 2