7

Suppose I have a program like this:

#include <iostream>
#include <string>
#include <vector>

// Takes values and outputs a string vector.
std::vector<std::string> foo(const int argc, char* args[]) {
    std::vector<std::string> strings;
    for (int i = 0; i < argc; i++)
        strings.push_back(args[i]);
    return strings;
}

int main(int argc, char *args[]) {

    std::vector<std::string> strings = foo(argc, args);

    for (unsigned int i = 0; i < strings.size(); i++)
        std::cout << strings[i] << std::endl;

    return 0;
}

Where the takeaway is that I'm trying pass the main() function's char** argument to another function or class. (I understand there are better ways to achieve what the above program does, my question is about passing char** arguments as read-only).

Questions:

  1. I've found that I can't make the second foo() argument const like the first. Why is this? A char** can't be converted to a const char**?
  2. I want to pass in this argument as "read-only". I'm not sure how to go about this, if it were say a string I'd pass it in via const reference, but I'm not sure how to go about this with pointers?
StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
Jaymaican
  • 149
  • 6
  • the question would be better if you included what you tried and the error message you get – 463035818_is_not_an_ai Feb 11 '19 at 11:26
  • 1
    A little OT, but if you want a vector of your command line arguments, you could create it like this: `std::vector args(argv+1, argv+argc);` (`argv+1` to skip the program name) – Ted Lyngmo Feb 11 '19 at 11:36

1 Answers1

9

I've found that I can't make the second foo() argument const like the first. Why is this? A char** can't be converted to a const char**?

If it were allowed, you'd be likely to break const correctness. Consider this

char const *a = "literl";
char *b = nullptr;
char const **c = &b; // This is not allowed in C++. Like your code.
*c = a; // The qualifications match, but now it's b that points at a literal.
*b = 'L'; // Modifying a string literal! Oops!

So there is a good reason to disallow it as written. But that doesn't mean you can't do what you want at all. Qualification conversions are possible so long as they are stricter. Well that's the gist of it anyway.

I want to pass in this argument as "read-only". I'm not sure how to go about this, if it were say a string I'd pass it in via const reference, but I'm not sure how to go about this with pointers?

Pass a pointer to a const pointer to a const char. Better write it in code to explain:

                                                         // The const here
std::vector<std::string> foo(int const argc, char const* const args[]) {
    std::vector<std::string> strings;
    for (int i = 0; i < argc; i++)
        strings.push_back(args[i]);
    return strings;
}

Why is this allowed? Because if we equate it to the bad example I started with, c can no longer be used to assign to b, and so we cannot get into an erroneous state.

StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
  • Thankyou so much for the detailed response! I understand now it's disallowed. I'm still a tiny bit confused on the syntax of `char const* const args[]`. I seem to be able to get `const char* const args[]` and `const char* const* args` to compile fine. I understand that they're likely the same thing, but what I'm stuck on is the syntax of it and which term is controlling what. Would you mind very briefly explaining each component of `char const* const args[]` and what it's doing to make the variable read-only? – Jaymaican Feb 11 '19 at 11:45
  • @Jaymaican - `args` is not immutable in either declaration. What *is* immutable, is what it's pointing at. So `*args` is `const`. We can't assign something invalid to it. And `**args` is const like you obviously wanted. Does that make more sense? – StoryTeller - Unslander Monica Feb 11 '19 at 11:47
  • Okay, that makes more sense, I think what confused me was the use of the const twice next to each other. I didn't know a "const pointer" was actually a thing! – Jaymaican Feb 11 '19 at 11:56
  • 1
    @Jaymaican - A pointer is an object. So it can be declared const like any other object :) – StoryTeller - Unslander Monica Feb 11 '19 at 11:58
  • It's becoming a lot more clear now. One final question/request I have that would be really beneficial is a breakdown of the syntax of `char const* const args[]`. I'm used to putting const on the left of everything and am a little confused with the syntax. Would you mind marking which component matches what phrase in your sentence "Pass a pointer to a const pointer to a const char". Also it might just be my device but the `// The const here` is lining up almost in the middle of the two const keywords :P – Jaymaican Feb 11 '19 at 12:52
  • 1
    @Jaymaican - `char const* const args[]` is exactly `char const* const *args` (I just didn't want to change your example too much). Now, read from right to left. `args` is a pointer to a const pointer to a const `char`. The qualifier applies to the thing on the left... except if there isn't anything there. For consistency, I always put it on the right when possible. – StoryTeller - Unslander Monica Feb 11 '19 at 12:54
  • 1
    I think there's a Q&A about the subject somewhere on SO too. You can look it up to learn more. – StoryTeller - Unslander Monica Feb 11 '19 at 12:56
  • Thanks very much, I completely understand now. :) I was just unclear syntactically with [] meaning *. – Jaymaican Feb 11 '19 at 13:03
  • Here's the [beautiful mathematical definition from the standard](https://timsong-cpp.github.io/cppwp/conv.qual) of allowed conversions (for some *standard* of beauty). Simply put (for some standard of simplicity): If you are going to add a `const` anywhere in e.g. `char ****`, then you must also add `const` in all following positions except the last: `char ****` -> `char *const ***` -> `char *const *const *const *`. – Arne Vogel Feb 11 '19 at 15:24