39

I have the following code:

glShaderSource(shader, 1, (const char **)data.c_str(), NULL);

But it makes my program crash. How do I convert std::string into const char ** ? I also tried (const char **)& but it said "requires l-value" which I don't understand. It works fine when I use this code:

const char *data = "some code";
glShaderSource(shader, 1, &data, NULL);

But I can't make it work directly from a std::string. I could allocate a new char array for it but that is not nice code.

I also tried with const GLchar but obviously it makes no difference.

Null
  • 1,950
  • 9
  • 30
  • 33
Rookie
  • 3,753
  • 5
  • 33
  • 33
  • 5
    Have a look at that function's documentation. There must be a reason it takes a pointer to a pointer. Supposedly this is an output parameter, and the function _writes_ a `char*` to the dereferenced `char**`. In that case you need to pass the address of a real pointer, and not of some temporary returned by `std::string::c_str()`. – sbi May 18 '11 at 16:12
  • 1
    @sbi: It's not an output parameter, it's expecting an array of c-strings. – Benjamin Lindley May 18 '11 at 16:15
  • 1
    @Benjamin: Ah, yeah, I forgot about that possibility. – sbi May 18 '11 at 16:22
  • 1
    I think this question is yet another of "why can't I do..." while the real question should be "why would you want to?". You're casting to an incompatible pointer type. It's not ever supposed to work. I think it's supposed to make you *stop* and *think* what is it that you don't understand about `glShaderSource`. A 15 second google would have told you that the 3rd argument "Specifies an array of pointers to strings containing the source code to be loaded into the shader.". The title of your question is wholly misleading: you **can't** do such a conversion. – Kuba hasn't forgotten Monica Sep 12 '14 at 14:32

7 Answers7

65

data.c_str() returns a const char*, so do this:

const char *c_str = data.c_str();
glShaderSource(shader, 1, &c_str, NULL);
Dark Falcon
  • 43,592
  • 5
  • 83
  • 98
  • 1
    is there a possibility to remove the variable declaration from it and make it to the one line where i call the function? – Rookie May 18 '11 at 16:02
  • 1
    @Rookie - Declare, but do not initialize, `c_str` somewhere: `const char*c_str;`. Then, at the point of use, try this: `glShaderSource(shader, 1, &(c_str=data.c_str()), NULL);` – Robᵩ May 18 '11 at 16:08
  • 6
    I don't think that you can use a one-liner. `glShaderSource` expects an array of strings (`const char**` = array of `const char*`), and your `std::string` just gives you one string. You have to add one level of indirection so that `glShaderSource` gets a pointer it can de-reference to retrieve the string. – Ferdinand Beyer May 18 '11 at 16:09
  • Hmm, Rob's way is an interesting way of doing it ... it still requires an assignment back to a memory address though, so you have to declare a variable that you can apply the address-of operator to somewhere. – Jason May 18 '11 at 16:11
  • 4
    @Rob: Note that `const char* c_str;` is a _[definition](http://stackoverflow.com/questions/1410563/what-is-the-difference-between-a-definition-and-a-declaration/1410632#1410632)_, not a declaration. – sbi May 18 '11 at 16:18
  • 3
    I perfectly agree with Ferdinand Beyer since the doc of `glShaderSource` explicitly tells you to use an array of `const char*`. – mister why May 18 '11 at 16:18
  • @sbi - re: *definition* and *declaration*. Agreed. Thanks! – Robᵩ May 18 '11 at 16:42
  • @FerdinandBeyer: That's what the `&` is for. – Lightness Races in Orbit Jun 30 '16 at 14:51
19

The return value of std::string::c_str() is a pointer value (i.e., an address) to a static string array held inside the data-structures of the std::string object. Since the return value is just a temporary r-value (i.e., it's just a number stored in a CPU register), it is not an l-value and therefore it does not have a memory address you can actually take the address of and cast to a pointer-to-pointer. You first must save the return pointer value in a memory address. Memory-locations are l-values, and can have the address-of operator applied to them. So that is why your second method (or Dark Falcon's method) works, although keep in mind that the pointer value returned is a temporary, meaning that if you do any operations on the std::string object, it could invalidate the pointer since the std::string object internally manages the memory of its data-structures. So just because you've saved the return pointer value in a memory location doesn't mean that the pointer won't be invalidated at some later time, and at a point that you may not be capable of deterministically choosing.

Jason
  • 31,834
  • 7
  • 59
  • 78
  • c_str() return can be assigned to l-value – CharlesB May 18 '11 at 15:58
  • 2
    @ CharlesB: Yes, the return value can be assigned to an l-value, but the return value itself is not an l-value, therefore you can't apply the address-of operator to the return of `std::string::c_str()`. – Jason May 18 '11 at 16:02
  • OK but your explanations are a bit out of the point, you don't need this knowledge to solve the OP problem – CharlesB May 18 '11 at 16:21
  • 2
    Sorry, I just thought it might help, and possibly give some background to the situation. The OP was wondering why one way worked, but another didn't. Printing up code that works is nice, but doesn't necessarily give one the tools or understanding to avoid similar circumstances in the future. Others had already created nice code samples, so I thought I would go a little more in-depth with the why one way works and another doesn't to help the OP's understanding of the root issue. – Jason May 18 '11 at 16:30
14

You can get a reasonable-looking call by using a helper class. Define this class:

struct StringHelper {
  const char *p;
  StringHelper(const std::string& s) : p(s.c_str()) {}
  operator const char**() { return &p; }
};

Then, when you need to call glShaderSource, do it this way:

glShaderSource(shader, 1, StringHelper(data), NULL);
Robᵩ
  • 163,533
  • 20
  • 239
  • 308
  • I used this. I had multiple strings to convert, and I could tuck the implementation code away and do everything in one line. Perfect. – GCon Nov 16 '13 at 23:44
7

glShaderSource signature is, according to glShaderSource doc:

void glShaderSource(
    GLuint shader,
    GLsizei count,
    const GLchar** string,
    const GLint* length);

where string "Specifies an array of pointers to strings containing the source code to be loaded into the shader". What you're trying to pass is a pointer to a NULL terminated string (that is, a pointer to a const char*).

Unfortunately, I am not familiar with glShaderSource, but I can guess it's not expected a pointer to "some code" but something like this instead:

const char** options =
{
    "option1",
    "option2"
    // and so on
};

From opengl-redbook, you can read an example (I've shortened it in purpose):

const GLchar* shaderSrc[] = {
    "void main()",
    "{",
    "    gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;",
    "}"
};
shader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(shader, NumberOfLines(shaderSrc), shaderSrc, NULL);
mister why
  • 1,967
  • 11
  • 33
  • 1
    `+1` for actually looking at the functions documentation! – sbi May 18 '11 at 16:21
  • @sbi and @mister, what is the point of splitting the string into lines like that? it works fine when all is in the first item in the array. – Rookie May 18 '11 at 18:29
  • @Rookie: This is what you want if you want to pass an array of more than one string. Since you only want to pass one string, I guess it doesn't apply to you. – sbi May 19 '11 at 09:57
2

I only want to point out that the pointer returned by c_str() is only valid as long as you don't do anything that requires reallocation of the internal buffer of std::string. That invalidates the pointer you got.

But since you really require a ** i would do this:

const char* mychararr[1] = {data.c_str()};
glShaderSource(shader, 1, mychararr, NULL);

That should work nicely as long as you don't leave the scope of mychararr.

RedX
  • 14,749
  • 1
  • 53
  • 76
0

Shader.cpp

#include "Shader.hpp"

Shader::Shader(GLenum type)
{
    this->_type = type;
}
Shader::~Shader() {}    

GLuint Shader::get(char* filename)
{
    GLuint shdr = glCreateShader(this->_type);
    FILE* f = 0;
    f = fopen(filename, "r+");
    char* str_tmp = 0;
    char** shdr_text = 0;
    shdr_text = (char**)malloc(sizeof(char**) * 255);
    str_tmp = (char*)malloc(sizeof(char*) * 255);
    int i = 0, ch = 0, n = 0;

    for(i = 0; i < 255; ++i){ *(shdr_text + i) = (char*)malloc(sizeof(char*) * 255); }

    i = 0;
    while((ch = fgetc(f)) != EOF)
    {
        sprintf(str_tmp, "%s%c", str_tmp, ch);
        if(ch == (int)'\n' || ch == (int)'\r')
        {
            sprintf(*(shdr_text + i), "%s", str_tmp);
            sprintf(str_tmp, "");
            ++i;
        }
    }

    free(str_tmp);
    fclose(f);

    glShaderSource(shdr, i, const_cast<const GLchar**>(shdr_text), 0);
    glCompileShader(shdr);

    free(shdr_text);

    return(shdr);
}

Shader.hpp

#ifndef SHADER_HPP
#define SHADER_HPP

#include <stdlib.h>
#include <stdio.h>
#include <GL/glew.h>
#include <GL/gl.h>

class Shader
{
    public:
        Shader(GLenum type);
        virtual ~Shader();

        GLuint get(char* filename);

    private:
        GLenum _type;
};

#endif
0

Try using the .c_str() it give you a char * that you can use as it worked for you b4

#include <string>

void ConversionSample ()
{
 std::string strTest ("This is a string");
 const char* pszConstString = strTest.c_str ();
 }
Luis Tellez
  • 2,785
  • 1
  • 20
  • 28