tags:

views:

137

answers:

5

A while ago I asked about std::string constants http://stackoverflow.com/questions/2312860/correct-idiom-for-stdstring-constants.

What I took away from that was not to use std::string constants but to use char string constants. So what the best idiom for that

#define FOO "foo"

const char * const FOO = "foo";

const char FOO[] = "foo";

Desirable features

  • get length at compile time. 1 & 3 but not 2 (sizeof doesnt work on 2)
  • can be included in .h without linker complaining. all (I think)
  • no multiple copies in .o, in linked output. depends on compiler (probably)

So it seems like #3 is best but scott meyers says to use #2 (effective c++ item #1)

summary of answers

  1. use jolly complicated template code
  2. use #3

The template code feels like overkill. So for now I go with #3;

But I will ruminate on the template code, the macroized version makes it look OKish; but I dont like the fact that its not portable (who knows, maybe gcc will decide that its wrong too)

A: 

Your desired features are contradictory.

  1. Length at compile time
  2. Defined in header file
  3. Single copy across compilation units

To get (1) and (2), you need to declare the variable as static or put it in an unnamed namespace. However, this will cause a definition in each compilation unit, which goes against (3).

To get (2) and (3), you need to declare the variable as extern, but then you won't get (1).

Now, if your linker is smart, it might optimize away the multiple copies, but I'm not sure if the standard allows it...

I recommend the const char FOO[] = "foo"; syntax declared in an unnamed namespace or as static if it need to be found in a specific namespace. If the string is very large, then I go for an extern.

André Caron
Uh, the features are not contradictory. Argh, now to explain that I'll have to add an answer. So be it. Cheers,
Alf P. Steinbach
#define works fine with sizeof
pm100
@pm100: it behaves like `static` in that it gives you (1) and (2) at the expense of generating a copy in each compilation unit (although it is guaranteed not to be generated unless it is actually used).
André Caron
A: 

This is how I see it. I wouldn't use any of those as it is. First, I'm inclined by #2, but, take into account that you have to declare the variable as extern in the .h, and select some .cpp to actually store the string:

// .h
extern const char*const STR;

// .cpp
const  char*const STR = "abc";

The only drawback, not having the length at run-time, doesn't seem to me a real reason to move to other option. If you have several strings, you can always have a set of integer constants (or #defines) to specify each string's length, such as STR_LEN. If you have a lot of them, you won't write them at hand anyway, and you can then generate automatically the ..._LEN constants at the same time.

Diego Sevilla
If the length is not needed at compile-time, then there is no more reason not to use `std::string`.
André Caron
@André: Maybe the overhead of the dynamic memory used by the `std::sting`. But yes, in general there is not much point on not using `std::string`.
Diego Sevilla
actually not - const * char const can safely be placed in multiple header files
pm100
+3  A: 

For the features that you want, ...

  • get length at compile time,
  • can be included in .h without linker complaining all,
  • no multiple copies in .o, in linked output,

... you can use the templated constant idiom, like

template< class Dummy >
struct Foo_
{
    static char const s[];
};

template< class Dummy >
char const Foo_<Dummy>::s[] = "Blah blah";

typedef Foo_<void> Foo;    // Now you can refer to Foo:s


#include <iostream>
using namespace std;
int main()
{
    cout << sizeof( Foo::s ) << " bytes: \"" << Foo::s << "\"\n";
}

You can wrap the generation in a macro.

However, as far as I know the only practical utility is to support char/wchar_t-agnostic code, and for that the pain may be larger than the gain.

EDIT:
MSVC versions 7.1 up through 10.0 incorrectly doesn't accept the sizeof. The following is a workaround that compiles nicely with g++ 4.4.1, Comeau Online 4.3.10.1, MSVC 7.1 and MSVC 10.0.

#include <stddef.h>

typedef ptrdiff_t   Size;

// Substitute a more general countOf
template< Size n >
struct SizedBuf { char sizer[n]; };

template< class Type, Size n >
SizedBuf< n > countOf_( Type (&)[n] ) { return n; }

#define COUNT_OF( array ) sizeof( countOf_( array ).sizer )

#define DEF_STRING( name, value )                               \
    template< class >                                           \
    struct name##_constant_                                     \
    {                                                           \
        static char const str[];                                \
        static Size const length    = COUNT_OF( value ) - 1;    \
    };                                                          \
                                                                \
    template< class Type >                                      \
    char const name##_constant_< Type >::str[] = value;         \
                                                                \
    template< class Type >                                      \
    Size const name##_constant_< Type >::length;                \
                                                                \
    typedef name##_constant_<void>  name;


DEF_STRING( a, "Argh, MSVC!" )
DEF_STRING( b, "Blah blah" )
DEF_STRING( c, "Currently there's no 'inline' for data in C++." )


#include <iostream>

template< char const* s >
void foo() { std::cout << "foo() says: " << s << std::endl; }

int main()
{
    using namespace std;

    int const x[a::length] = {};    // Showing off compile time constant.
    foo<a::str>();                  // Showing off external linkage.

    cout << a::length << " characters: \"" << a::str << "\"." << endl;
}

Cheers & hth.,

Alf P. Steinbach
+1. Nice idea. Crazy, but nice :)
Diego Sevilla
Care to explain exactly what benefit you get over the `const char FOO[] = "foo";` syntax? I have a hard time going through *both* a weird template and a macro, as well as loosing support for easily changing from `char` to `wchar_t` and still generating a copy in each compilation unit...
André Caron
Alf P. Steinbach
Thinking about it, tt *does* reduce the number of copies to only the compilation units where the value is used, just like the macro version. However, you still get a macro and a weird template :-)
André Caron
Alf P. Steinbach
@Alf: your solution does not strictly conform the the 3rd requirement either. You still get a template instantiation in each compilation unit because of the `typedef`, hence a copy of the string in each compilation unit.
André Caron
Alf P. Steinbach
@Alf: [I macro'd your answer](http://stackoverflow.com/questions/3992237/correct-idiom-for-character-string-not-stdstring-constants-in-c/3992523#3992523). It doesn't work in VS (doesn't see the definition of `s` (`value` in my answer) so doesn't know how to compute the size. Any fix you know?
GMan
hmm, an answer this complex tell me something is wrong with c++. I mean I am not asking for anything complicated or unusual. To have to go to such length seems extraordinary. representation neutral (1,2,4 byte chars etc) is a more complex need and I can see that this would be good for that. But for "hello world" it seems like overkill
pm100
@Alf: the standard says static class (in general) members will have external linkage and I couldn't find an exception for template classes. Just tested on MSVC and it seems to agree with you. If seems it does address all 3 of the OP's requirements, but I find it ugly (no offense :-).
André Caron
@André: in theory it's fine, but GMan pointed out that MSVC 10.0 doesn't like `sizeof` on this thing, i.e. it incorrectly regards the type as incomplete (g++ and Comeau have no problems). I checked and the MSVC bug goes back to at least version 7.1, which is the oldest I have. Also, I'm unable to find MSVC bug workaround. :-(
Alf P. Steinbach
Added workaround for the practical case of defining string constants (or more generally array constants). I still don't know any general workaround for the MSVC bug as such.
Alf P. Steinbach
A: 

I think you took the wrong idea away from your previous question.
Strings tend to be sparse and not good for lookup, use numbers instead.

You still don't seem to get the difference between declaring in a .h and defining the storage in a .cpp thus avoiding multiple copies. Even if you had multiple copies (with different constant names) you would still have the issue you mentioned from your previous question.

has one fatal flaw. I cannot have static module level code that uses these strings since they might not have been constructed yet

The only way to avoid this is to bring the constants into the same scope with what you currently have at static module level.

Put all the related stuff in a class!

Greg Domjan
para 2- I understand precisely the differences. In some cases we are talking about harmless duplication of literals, in other cases we are talking about linker barfs. I know exactly what produces linker barfs and what does not. In my desirable feature list I have as a high need - no linker barfs, and as a nice to have ; no bloat
pm100
second the issues regarding class has nothing to do with it. If I have global statics then the load order is not defined. I can rearrange my code so that I dont have global static, but thats not the point. I am observing that if you do have global static that the suggestion doesnt work
pm100
A: 

This is just Alf's answer macro'd:

#include <iostream>

#define string_constant(pName, pLiteral)                    \
        template <typename = void>                          \
        struct pName##_detail                               \
        {                                                   \
            static const char value[];                      \
        };                                                  \
                                                            \
        template <typename T>                               \
        const char pName##_detail<T>::value[] = pLiteral;   \
                                                            \
        typedef pName##_detail<> pName

string_constant(my_constant, "this is a literal");

int main()
{
    std::cout << my_constant::value << std::endl;
    std::cout << sizeof my_constant::value << std::endl;
}

codepad. Doesn't seem to work in VS2010. :/

GMan
Alf P. Steinbach
@Alf: That shouldn't make a difference (it's just cleaner to me). But I changed it anyway just to be sure, same error. Looks like yet another case of non-compliance. (FFS)
GMan
@GMan: g++ and Comeau online accept the code, MSVC 10 (and I also checked with 7.1) do not. Checking template param matching it seems that MSVC incorrectly regards the array's type as incomplete in `main`. In C++0x draft N3092 §3.9/6 discusses the completion of an array type within a translation unit (going from incomplete to complete when size becomes known). I'm sorry, I don't know of an MSVC workaround. I tried a few things, none worked. :-(
Alf P. Steinbach
@ALf: :( Oh well. Thanks for the time and neat trick. :)
GMan
@GMan: as long as its wrapped by a macro anyway, the macro can infer the length and define a length constant. I've added example code for that to my answer. Cheers,
Alf P. Steinbach