Thread: Declaring a type without any definition

  1. #1
    Registered User
    Join Date
    Feb 2019
    Posts
    3

    Declaring a type without any definition

    Hello everybody.

    (English is not my native tongue. Also, I am a bit ill (nothing serious, just winter etc) and it's late. Please forgive me for my poor syntax and for my errors).

    I use to have headers with opaque pointers. They contain something like:

    Code:
    struct Foo;
    typedef struct Foo Bar;
    
    // function that returns a pointer to Bar
    // functions that take a pointer to Bar as parameter
    And most of times it's ok.
    However, when I do that I am bound to use a struct. Most of time it is a struct, and a "fresh" brand new struct with that. So it's ok.
    But sometime, I could use an "int" or even reuse an other existing type.

    If, in my source code, I write something like this:
    Code:
    typedef int Bar;
    The compiler will issue an error. Which is understandable : whatever "struct Foo" is (I didn't give a definition to that struct, I just said to the compiler that "it exists") it is not an int (which isn't a struct at all).

    Same thing, If I write
    Code:
    typedef struct Blaaaa Bar;
    => error. struct Foo is something, the compiler doesn't know what, but it isn't "struct Blaaaa".

    Same for
    Code:
    typedef FILE Bar;
    (it's really the same, but it's a bit more concrete example).

    The problem is I am giving too much informations. I don't want to say that "Bar" is an alias for "struct Foo". I just want to say that "there exists a type named Bar".

    P.S. :
    I am not declaring function returning a Bar or taking a Bar as parameter. The only use pointers to Bar.
    I also know that I can somehow circumvent the problem by this
    Code:
    #ifndef THE_C_SOURCE_FILE
    struct Foo;
    typedef struct Foo Bar;
    #endif
    by hiding the declaration from the C source file actually giving implementation to the functions below.
    Conditional compilation : "for the rest of the world, Bar is an alias to struct Foo, whatever this struct is. For the compilation unit giving an implementation to the function using Bar, Bar is an alias for whatever I want".
    That way, I can in this compile unit give a fresh definition for Bar (and alias it to int, for example). But it doesn't seem clean : Pointers may have different sizes and doing this may result in incorrect functions calls (the caller pushing not enough data on the stack, or too much).

    Is there a way, in C (and pure C) to actually do something like this:

    Code:
    typedef Bar;
    
    //or
    This_type_exists_shutup_compiler Bar;
    I don't think so, but It costs nothing to ask. I am a professional programmer (master 2 degree) but there may still be a hidden feature of the language that I don't know.
    A mystic magic keyword from ancient legends that nobody saw for ages and which the very existence is still unsure.
    Thanks for your answers.
    Have a nice night.

    P.S.2 : I won't be able to read answers before tomorrow morning at roughly the same hour.

  2. #2
    Registered User
    Join Date
    Dec 2017
    Posts
    1,628
    If I understand your question, the answer is that it must be a struct.
    Some actual code might clarify your question.
    BTW, what is a "master 2 degree"?

    A little inaccuracy saves tons of explanation. - H.H. Munro

  3. #3
    Programming Wraith GReaper's Avatar
    Join Date
    Apr 2009
    Location
    Greece
    Posts
    2,738
    void pointers exist to solve these kinds of problems. Just do something like this (the preprocessor is "pure C" ):
    Code:
    // In the header file
    #define Bar void
    
    // In the source file
    #undef Bar
    #define Bar struct Foo
    or, if for some reason you don't want to use the preprocessor, you could typedef void:
    Code:
    // In the header file
    typedef void Bar;
    
    // In the source file
    struct foo* fooPtr = (struct foo*)barPtr;
    Last edited by GReaper; 02-05-2019 at 10:53 PM.
    Devoted my life to programming...

  4. #4
    C++ Witch laserlight's Avatar
    Join Date
    Oct 2003
    Location
    Singapore
    Posts
    28,413
    I'd say that you have two choices:
    • Change the layout of struct Foo such that it has an int member or a struct Bar member, whatever the case may be, then implement the functions associated with struct Foo using this member, possibly by forwarding. This is after all the original conception of the opaque pointer idiom, that you can freely change the layout of the hidden struct without forcing all translation units that include the header to recompile.
    • Change the typedef in the header. Yes, this breaks the benefit of the opaque pointer idiom as every translation unit that includes the header has to be recompiled, but that's the price you have to pay for not wanting to actually use the opaque pointer idiom by changing the layout; instead you actually want to change the typedef.


    EDIT:
    Quote Originally Posted by GReaper
    void pointers exist to solve these kinds of problems.
    Not exactly. The void pointer approach is different from the opaque pointer approach. You cannot redefine Bar in the source file because then the functions declared in the header will have different signatures from the functions defined in the source file.
    Last edited by laserlight; 02-05-2019 at 10:55 PM.
    Quote Originally Posted by Bjarne Stroustrup (2000-10-14)
    I get maybe two dozen requests for help with some sort of programming or design problem every day. Most have more sense than to send me hundreds of lines of code. If they do, I ask them to find the smallest example that exhibits the problem and send me that. Mostly, they then find the error themselves. "Finding the smallest program that demonstrates the error" is a powerful debugging tool.
    Look up a C++ Reference and learn How To Ask Questions The Smart Way

  5. #5
    Programming Wraith GReaper's Avatar
    Join Date
    Apr 2009
    Location
    Greece
    Posts
    2,738
    Quote Originally Posted by laserlight View Post
    You cannot redefine Bar in the source file because then the functions declared in the header will have different signatures from the functions defined in the source file.
    Isn't this a problem of C++ in particular and not C? Of course, I know that C++ gives different signatures to the functions depending on what their arguments are, but I'm quite certain C doesn't do that.

    In C++ though, type-casting would be the only way to use void pointers.
    Devoted my life to programming...

  6. #6
    C++ Witch laserlight's Avatar
    Join Date
    Oct 2003
    Location
    Singapore
    Posts
    28,413
    Quote Originally Posted by GReaper
    Isn't this a problem of C++ in particular and not C? Of course, I know that C++ gives different signatures to the functions depending on what their arguments are, but I'm quite certain C doesn't do that.
    What you're saying then, is that C would treat this function:
    Code:
    void foo(void *x);
    as equivalent to this function:
    Code:
    void foo(struct Foo *x);
    as long as you try and confuse things by a #define or a typedef. That sounds absurd to me, but then as I have never tried I concede that I could be mistaken, so I tried:
    Code:
    #ifndef CBOARD_BAR_H
    #define CBOARD_BAR_H
    
    #define Bar void
    
    Bar *Bar_init(void);
    void Bar_destroy(Bar *bar);
    void Bar_print(Bar *bar);
    
    #endif
    
    int main(void)
    {
        Bar *bar = Bar_init();
        if (bar)
        {
            Bar_print(bar);
            Bar_destroy(bar);
        }
        return 0;
    }
    
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    struct Foo
    {
        char text[100];
    };
    
    #undef Bar
    #define Bar struct Foo
    
    Bar *Bar_init(void)
    {
        Bar *bar = malloc(sizeof(*bar));
        if (bar)
        {
            strcpy(bar->text, "Hello world!");
        }
        return bar;
    }
    
    void Bar_destroy(Bar *bar)
    {
        free(bar);
    }
    
    void Bar_print(Bar *bar)
    {
        printf("%s\n", bar->text);
    }
    gcc 7.3.0 reports these errors when compiling with -Wall -pedantic:
    Code:
    test.c:35:6: error: conflicting types for ‘Bar_init’
     Bar *Bar_init(void)
          ^~~~~~~~
    test.c:6:6: note: previous declaration of ‘Bar_init’ was here
     Bar *Bar_init(void);
          ^~~~~~~~
    test.c:45:6: error: conflicting types for ‘Bar_destroy’
     void Bar_destroy(Bar *bar)
          ^~~~~~~~~~~
    test.c:7:6: note: previous declaration of ‘Bar_destroy’ was here
     void Bar_destroy(Bar *bar);
          ^~~~~~~~~~~
    test.c:50:6: error: conflicting types for ‘Bar_print’
     void Bar_print(Bar *bar)
          ^~~~~~~~~
    test.c:8:6: note: previous declaration of ‘Bar_print’ was here
     void Bar_print(Bar *bar);
          ^~~~~~~~~
    Perhaps you were thinking of the implicit conversion from void* to a pointer to object type instead, since that exists in C but not C++? But that's a different thing from replacing the opaque pointer idiom with void*.
    Quote Originally Posted by Bjarne Stroustrup (2000-10-14)
    I get maybe two dozen requests for help with some sort of programming or design problem every day. Most have more sense than to send me hundreds of lines of code. If they do, I ask them to find the smallest example that exhibits the problem and send me that. Mostly, they then find the error themselves. "Finding the smallest program that demonstrates the error" is a powerful debugging tool.
    Look up a C++ Reference and learn How To Ask Questions The Smart Way

  7. #7
    Programming Wraith GReaper's Avatar
    Join Date
    Apr 2009
    Location
    Greece
    Posts
    2,738
    Oh... yeah, you're right. I guess I was thinking of the linking phase and subconsciously ignored the compilation phase...
    Devoted my life to programming...

  8. #8
    Registered User
    Join Date
    May 2012
    Location
    Arizona, USA
    Posts
    948
    Here's how I handle opaque types:

    bar.h:
    Code:
    struct Bar;
    typedef struct Bar Bar; // this isn't necessary in C++
    extern void foo(Bar *);
    extern int quack(const Bar *);
    bar.c:
    Code:
    #include "bar.h"
    
    struct Bar {
        int whatever;
    };
    
    void foo(Bar *bar)
    {
        bar->whatever = 42;
    }
    
    int quack(const Bar *bar)
    {
        return bar->whatever;
    }
    This has the advantage that your type has the same name (Bar) both inside and outside the implementation source code file (bar.c); only bar.c knows what the "Bar" type looks like.

    It also has the advantage that C will not let you pass just any type of pointer to the "foo" and "quack" functions; using pointer to void lets you shoot yourself in the foot much more easily.

  9. #9
    C++ Witch laserlight's Avatar
    Join Date
    Oct 2003
    Location
    Singapore
    Posts
    28,413
    christop, that's just the typical opaque pointer idiom. It doesn't answer this question because the OP has a weird edge case where the OP might want to use a built-in type instead, or instead of changing struct layout or renaming the new (third party?) struct that is to be used for a re-implementation, the OP apparently wants to redesignate the typedef wiithout causing recompilations (hence what appears to be an attempt to keep the original typedef in the header while changing it in the source file, thus the errors referred to).

    Incidentally, is it your general style to use extern for functions even though they have external linkage by default?
    Quote Originally Posted by Bjarne Stroustrup (2000-10-14)
    I get maybe two dozen requests for help with some sort of programming or design problem every day. Most have more sense than to send me hundreds of lines of code. If they do, I ask them to find the smallest example that exhibits the problem and send me that. Mostly, they then find the error themselves. "Finding the smallest program that demonstrates the error" is a powerful debugging tool.
    Look up a C++ Reference and learn How To Ask Questions The Smart Way

  10. #10
    Registered User
    Join Date
    May 2012
    Location
    Arizona, USA
    Posts
    948
    Quote Originally Posted by laserlight View Post
    christop, that's just the typical opaque pointer idiom. It doesn't answer this question because the OP has a weird edge case where the OP might want to use a built-in type instead, or instead of changing struct layout or renaming the new (third party?) struct that is to be used for a re-implementation, the OP apparently wants to redesignate the typedef wiithout causing recompilations (hence what appears to be an attempt to keep the original typedef in the header while changing it in the source file, thus the errors referred to).
    Oh, I must have missed that part. What would be a use case for doing that? Is it to implement some sort of template/generic interface?

    Quote Originally Posted by laserlight View Post
    Incidentally, is it your general style to use extern for functions even though they have external linkage by default?
    Yes, it's somewhat of a habit of mine to put "extern" in front of all declarations in a header file, for both variables and functions, even if it's unnecessary for functions.

  11. #11
    Registered User
    Join Date
    Feb 2019
    Posts
    3
    Quote Originally Posted by john.c View Post
    If I understand your question, the answer is that it must be a struct.
    Some actual code might clarify your question.
    BTW, what is a "master 2 degree"?

    I don't have code with me today, I'll have some tomorrow.
    But there isn't really a recurrent case where it happens.

    Right now, there is two case I have in my mind:
    1/
    Any struct that will only contains a single variable would do it.
    We can imagine a "counter". We only need an integer.
    The "create_counter" function could allocate an integer, set it to 0 and returns it.
    The "counter_destroy" function would just free it.
    The "counter_next" function would just increment it and return the value.

    We don't need a struct.

    2/
    If the struct contains a single data AND the data is a pointer, there will two level of indirection (resolve the struct from its address. Then take the pointer into that struct, and resolve it).

    For the "master 2 degree", sorry, I thought that it was common in many countries. It's "just before thesis". It's 5 years after "baccalaureate" if you know that.
    If you don't, "baccalaureate" is basically the exam you'll have to pass when you'll be around 18 years old, and after which you'll decide what you want to study "for real".
    I just wanted to clarify my situation, and made it less clear

    Quote Originally Posted by laserlight View Post
    I'd say that you have two choices:
    Change the layout of struct Foo such that it has an int member or a struct Bar member, whatever the case may be, then implement the functions associated with struct Foo using this member, possibly by forwarding. This is after all the original conception of the opaque pointer idiom, that you can freely change the layout of the hidden struct without forcing all translation units that include the header to recompile
    Wow,you are going way too far . I am not talking of anything needing to recompile the whole program.
    I am just talking about making the opaque pointer more opaque.
    Maybe I should have start with this.
    As it is, the opaque pointer to Bar is not completely opaque. It allows other compilation unit to know that "Bar is an alias for struct Foo".
    So it allow other compilation units to know that there is a struct under the coat.
    I just wanted to have it more opaque : "Bar is a type, and that's all you need to know".

    As it is now, I feel like I am using a trick to have opaque pointers.
    I make a forward declaration of a struct, then I declare an alias for this struct ... it's not clean.
    It's why I was talking about a mystic keyword. Something like
    Code:
    opaquetype Bar;
    You didn't mention it but I can also
    • cast the parameter (a "Bar *") to the actual type I want (a "int *", a "FILE *" etc).


    Quote Originally Posted by GReaper View Post
    Oh... yeah, you're right. I guess I was thinking of the linking phase and subconsciously ignored the compilation phase...
    And it doesn't work for the link part either.
    ...
    Ok, let's talk a bit about it.
    On most computers it will work on THIS example.

    But on some computer you'll have issues as if you did so;mething like this
    Code:
    int a_function(int a, int b)
    {
      return a + b;
    }
    
    int main()
    {
      int (*single_arg_pointer) (int);
    
      // casting a 2-parameters function to a 1-parameter pointer
      // the problem is not about "function" and "pointer".
      // it's about "2-parameters" vs "1-parameter"
      single_arg_pointer = (int (*) (int)) a_function;
      single_arg_pointer(5);
    
    
      return 0;
    }
    Or if in the same way you were casting a function taking an int to a function taking a char etc.

    This is because not all pointers have the same size.

    Yeah, I know, it sounds weird.
    But first let's talk about the consequence of this.
    If you cast "a function taking a pointer to A" to "a function taking a pointer to B" you may be actually casting "a function taking a parameter of some size" to "a function taking a parameter of some other size".
    This is for the consequence.

    For the "not all pointers have the same size" ... well, according to the documentation of dlsym
    compilers conforming to the ISO C standard are required to generate a warning if a conversion from a void * pointer to a function pointer is attempted
    But you can search more information in your preferred search engine
    "pointer different size C"
    (It's starting to be a bit late and this message is already too long, sorry).

    Quote Originally Posted by laserlight View Post
    It doesn't answer this question because the OP has a weird edge case where the OP might want to use a built-in type instead, or instead of changing struct layout or renaming the new (third party?)
    You make me feel bad (sad there isn't a crying smiley ^^).
    But you'll laugh at me and say I do ugly code ...

    Ok, imagine you have a fantastic macro like this

    SET_CODE_OF(int)

    that generate functions returning a "int_set", functions taking a "int_set" as parameter and allowing you to add, remove, check if the set contains a specific int, etc. Yes, just like a template, but shhhh.

    Now I ask you to create an opaque pointer to handle ids of clients. Or anything basically being a set of ints. What you want to do is something like this:

    ids.h
    Code:
    //guard headers
    
    opaquetype IDs;
    
    IDs * create_IDs();
    void IDs_destroy(IDs *);
    
    void IDs_add(IDs *, int);
    void IDs_remove(IDs *, int);
    
    //etc
    and, in your C

    Code:
    //includes
    
    SET_CODE_OF(int)
    typedef int_set IDs;
    
    IDs * create_IDs() { return create_int_set(); }
    void IDs_destroy(IDs * ids) { int_set_destroy(ids); }
    
    void IDs_add(IDs * ids, int i) { int_set_add(ids, i); }
    void IDs_remove(IDs * ids, int i) { int_set_remove(ids, i); }
    
    //etc
    Which would be valid from every point-of-view, because IDs would have been declared without any definition (not even "being an alias of a struct").
    If later I say to the compiler "about nothing : IDs is an alias for int_set" I am not contradicting myself.
    Other compilation units only know that "IDs is a type".
    I would not be doing any cast, not be doing any "box containing a box containing a box".
    And if tomorrow I need something more than just an "int_set", I still can revert back to the old struct approach. The .h will remain the same.

    The int_set does not appears in the ".h" it's only a tool for implementation, and never appears in contracts formed by ".h".
    I am not bound to use it, and still I can use it and make my life simpler.

  12. #12
    C++ Witch laserlight's Avatar
    Join Date
    Oct 2003
    Location
    Singapore
    Posts
    28,413
    Quote Originally Posted by bubuche
    Wow,you are going way too far . I am not talking of anything needing to recompile the whole program.
    Then you don't understand the opaque pointer idiom.

    Quote Originally Posted by bubuche
    As it is, the opaque pointer to Bar is not completely opaque. It allows other compilation unit to know that "Bar is an alias for struct Foo".
    So it allow other compilation units to know that there is a struct under the coat.
    And what's wrong with that? The idea behind the opaque pointer idiom is that it hides the internals of a library from the user of the library. The main benefit of this is that the library maintainer can change the internals of the library at will, such that users of the library just need to obtain the updated library and (compile the library and) link to it. If the opaque pointer idiom was not used, a change to the internals of the library could require the user of the library to recompile their own code that uses the library, because the header of the library has changed, thus affecting their own code. Of course, as another benefit it also means that the library owner can release a header and library file without source such that users of the library will have a somewhat harder time reverse engineering the internals of the library.

    Quote Originally Posted by bubuche
    I just wanted to have it more opaque : "Bar is a type, and that's all you need to know".
    What you are asking for is impossible. If it is a type, it must be either a built-in type, an enum type, a struct type, a union type, function type, a pointer type or array type derived from these types, etc. It cannot simply be "a type" in the sense of say, a type name as a template parameter in C++.

    Quote Originally Posted by bubuche
    You didn't mention it but I can also
    • cast the parameter (a "Bar *") to the actual type I want (a "int *", a "FILE *" etc).
    No, you cannot do that, unless you want to have undefined behaviour. From your understanding that not all pointers have the same size, you already know this.

    Quote Originally Posted by bubuche
    Ok, imagine you have a fantastic macro like this

    SET_CODE_OF(int)

    that generate functions returning a "int_set", functions taking a "int_set" as parameter and allowing you to add, remove, check if the set contains a specific int, etc. Yes, just like a template, but shhhh.

    Now I ask you to create an opaque pointer to handle ids of clients. Or anything basically being a set of ints. What you want to do is something like this:
    I have already described how you can do this if you want to use the opaque pointer idiom, i.e., make the internal struct a wrapper for your third party type and do function forwarding:
    ids.h:
    Code:
    //guard headers
    struct IDs;
    typedef struct IDs IDs;
    
    IDs *create_IDs();
    void IDs_destroy(IDs *);
    
    void IDs_add(IDs *, int);
    void IDs_remove(IDs *, int);
    
    //etc
    ids.c:
    Code:
    //includes
    
    SET_CODE_OF(int)
    
    struct IDs
    {
        int_set *values;
    };
    
    IDs * create_IDs(void)
    {
        IDs * ids = malloc(sizeof(*ids));
        if (ids)
        {
            ids->values = create_int_set();
        }
        return ids;
    }
    
    void IDs_destroy(IDs * ids)
    {
        int_set_destroy(ids->values);
        free(ids);
    }
    
    void IDs_add(IDs * ids, int i)
    {
        int_set_add(ids->values, i);
    }
    
    void IDs_remove(IDs * ids, int i)
    {
        int_set_remove(ids->values, i);
    }
    
    //etc
    If you don't want to do this because the wrapper does do a little more work than directly using the library generated by SET_CODE_OF(int), then ditch the opaque pointer idiom and expose the implementation:
    Code:
    ids.h:
    //guard headers
    
    SET_CODE_OF(int)
    typedef int_set IDs;
    
    IDs * create_IDs();
    void IDs_destroy(IDs *);
    
    void IDs_add(IDs *, int);
    void IDs_remove(IDs *, int);
    
    //etc
    If you don't want to do this either, perhaps because you really don't want to expose the implementation (for the reasons why people use the opaque pointer idiom in the first place, as I mentioned above), then there's the use of void* as GReaper suggested, but without confusing it with the opaque pointer idiom:
    ids.h:
    Code:
    //guard headers
    typedef void IDs;
    
    IDs *create_IDs();
    void IDs_destroy(IDs *);
    
    void IDs_add(IDs *, int);
    void IDs_remove(IDs *, int);
    
    //etc
    ids.c:
    Code:
    //includes
     
    SET_CODE_OF(int)
     
    IDs * create_IDs() { return create_int_set(); }
    void IDs_destroy(IDs * ids) { int_set_destroy(ids); }
     
    void IDs_add(IDs * ids, int i) { int_set_add(ids, i); }
    void IDs_remove(IDs * ids, int i) { int_set_remove(ids, i); }
     
    //etc
    This comes close to what you originally envisioned, except that it carries the negative point christop mentioned in post #8: it is not type safe since your library's interface deals with a void* rather than a type specific to the library, so in theory a user of your library is more likely to make a mistake and provide a pointer to something other than int_set (which they never see, like in the opaque pointer idiom). The other thing would be that if you want to work with int_set* rather than void* because you want to do something more sophisticated than function forwarding, you then need to cast or to have an explicit int_set* local variable, and this inconvenience will remain forever even if you revert to using your own internal struct.
    Last edited by laserlight; 02-06-2019 at 10:58 PM.
    Quote Originally Posted by Bjarne Stroustrup (2000-10-14)
    I get maybe two dozen requests for help with some sort of programming or design problem every day. Most have more sense than to send me hundreds of lines of code. If they do, I ask them to find the smallest example that exhibits the problem and send me that. Mostly, they then find the error themselves. "Finding the smallest program that demonstrates the error" is a powerful debugging tool.
    Look up a C++ Reference and learn How To Ask Questions The Smart Way

  13. #13
    Registered User
    Join Date
    Feb 2019
    Posts
    3
    Quote Originally Posted by laserlight View Post
    Then you don't understand the opaque pointer idiom.
    I perfectly understand what it is. And, for this reason, if I don't want anything that would suppose me to recompile anything more that the compilation unit of Bar.


    And what's wrong with that? The idea behind the opaque pointer idiom is that it hides the internals of a library from the user of the library.
    And it's what I want, and what I don't have.
    With the approach I have now, I hide most of the internals, but not all of them.
    One thing I don't hide is that Bar is actually an alias (a synonym) for "struct Foo".
    This is internal stuff. This shouldn't be in the .h
    Other compilation units doesn't know what "struct Foo" actually is (which is a good thing) but they now that I am using a struct Foo.
    They don't have to know this ! We only need to agree on a type ("Bar"). They don't need to know what "Bar" is for me.
    I will never "return a Bar", I will never "take a Bar as a parameter".
    I will only "return a pointer to a Bar", or "take a pointer to a Bar as a parameter".

    Actually, the compiler can (and will) handle it internally as a void*. The only thing I add by this is that if a user give "Bar *" to a function taking a "Thing *" (both opaque pointers) the compiler will emit a warning, which it wouldn't with typedefs to void.

    [QUOTE]
    The main benefit of this is that the library maintainer can change the internals of the library at will, such that users of the library just need to obtain the updated library and (compile the library and) link to it.
    [QUOTE]

    And I can't change the internals at will. I do can change the content of the struct at will. But I can't change that I am using a struct, or using an int, or actually using an other opaque type.
    With that line
    Code:
    typedef struct Foo Bar;
    I am exposing internals into the .h


    What you are asking for is impossible. If it is a type, it must be either a built-in type, an enum type, a struct type, a union type, function type, a pointer type or array type derived from these types, etc. It cannot simply be "a type" in the sense of say, a type name as a template parameter in C++.
    A template parameter in C is not even a type, but whatever. A type needs to be one of the thing you mention only if you declare a variable of that type (or return a value of that type, or pass it as parameter etc.)
    To say it more clearly : as soon as we need its size to store it somewhere into the memory, on the stack etc.
    As long as you handle pointers only (with the exception of pointers to functions) it's only an address.

    No, you cannot do that, unless you want to have undefined behaviour. From your understanding that not all pointers have the same size, you already know this.
    Yes I can do that, and no I will not have an undefined behavior.
    Not all integers have the same size.
    Do you think that
    Code:
    short a = 1;
    long b = 2;
    
    a = (short) b;
    will create an undefined behaviour ?
    No it won't.
    And for my case it will either work (very likely on most computer) or generate a warning/error.
    Code:
    int a = 1;
    int b;
    b = *(int *)(short *)(long *)(void *)(char *) & a;
    Will not cause an undefined behaviour. In the same way
    Code:
    struct Foo;
    
    struct Foo * create_a_foo()
    {
      return (struct Foo*) fopen("bla.txt", "r");
    }
    
    void foo_destroy(struct Foo * foo)
    {
      fclose( (FILE*) foo );
    }
    Will always work (even more here, because I am strictly speaking casting a "pointer to a struct" into an other "pointer to a struct").
    Nobody can write
    Code:
    struct Foo a;
    Because the compiler doesn't know what struct Foo actually is. The only way a "struct Foo *" may arrive in the program is by calling "create_a_foo".
    So every "struct Foo *" will be a FILE* in disguise.
    No undefined behaviour.

    And in a more simple way : I am not fooling the compiler. I am blatantly casting a pointer to thing to an other pointer to a thing. If the cast doesn't make sense, the compiler will refuse to compile.



    Code:
    //includes
    
    SET_CODE_OF(int)
    
    struct IDs
    {
        int_set *values;
    };
    //etc
    And then I have one more "pointer" (dereferencing ?) level.

    This comes close to what you originally envisioned, except that it carries the negative point christop mentioned in post #8: it is not type safe since your library's interface deals with a void* rather than a type specific to the library, so in theory a user of your library is more likely to make a mistake and provide a pointer to something other than int_set (which they never see, like in the opaque pointer idiom). The other thing would be that if you want to work with int_set* rather than void* because you want to do something more sophisticated than function forwarding, you then need to cast or to have an explicit int_set* local variable, and this inconvenience will remain forever even if you revert to using your own internal struct.
    I just came here to see if someone had a mystic keyword.
    As you say it yourself, the problem with void is that during compilation I will have less type check.
    You are not talking about any running time problem.
    I was talking about something producing the same binaries as the "void" solution and providing the same compilation verification as the opaque pointer approach.
    It's two different worlds.

    And, actually : when you use opaque pointers, the binaries produced are the same as when you use void pointers. And casts between pointers most (if not all) of times are translated into ... nothing. They don't exist in the binary : an address is an address.
    Types are not preserved at runtime or in the binaries.

    Well, except if somebody has the solution (which probably doesn't exist) I am looking for, I think there is no point in this discussion anymore.

  14. #14
    C++ Witch laserlight's Avatar
    Join Date
    Oct 2003
    Location
    Singapore
    Posts
    28,413
    Quote Originally Posted by bubuche
    One thing I don't hide is that Bar is actually an alias (a synonym) for "struct Foo".
    That's traditionally not regarded as part of the internals when using the opaque pointer idiom though, for the reasons I have outlined in my earlier posts: the layout of the internal struct can change to accommodate whatever changes to the internals need to be made. In your case, you just don't want to do that, which is fine, but it doesn't mean that the opaque pointer idiom doesn't hide that one aspect of the internals.

    Quote Originally Posted by bubuche
    As long as you handle pointers only (with the exception of pointers to functions) it's only an address.
    Not quite: a pointer in C is not merely an address, but an address with an associated type, without which pointer arithmetic wouldn't work (as is the case for void* after all, since it is that pointer to object type that doesn't provide any associated object type information).

    Quote Originally Posted by bubuche
    Do you think that
    Code:
    short a = 1;
    long b = 2;
     
    a = (short) b;
    will create an undefined behaviour ?
    That's irrelevant because you're talking about pointer conversion, not integer conversion. Incidentally, your example, with a different value for b, has the potential to give an implementation-defined result or raise an implementation-defined signal. Of course, that isn't undefined behaviour, and it is generally a well understood implementation-defined result.

    Quote Originally Posted by bubuche
    In the same way
    Code:
    struct Foo;
     
    struct Foo * create_a_foo()
    {
      return (struct Foo*) fopen("bla.txt", "r");
    }
     
    void foo_destroy(struct Foo * foo)
    {
      fclose( (FILE*) foo );
    }
    Will always work (even more here, because I am strictly speaking casting a "pointer to a struct" into an other "pointer to a struct").
    You are mistaken. It might work, but it is not guaranteed to "always work", in which case you end up with undefined behaviour. Refer to the C standard:
    Quote Originally Posted by C11 Clause 6.3.2.3 Paragraph 7a
    A pointer to an object type may be converted to a pointer to a different object type. If the resulting pointer is not correctly aligned for the referenced type, the behavior is undefined.
    Because the behaviour is undefined, your claim that "if the cast doesn't make sense, the compiler will refuse to compile" is too optimistic: for undefined behaviour, no diagnostic is required, and this may be especially so when casting from a pointer to one object type to a pointer to another object type (as opposed to say, casting from a pointer to object type to a pointer to function type) since the compiler might not check for alignment. Rather, for what you have in mind, using a void* is the proper way that will always work:
    Quote Originally Posted by C11 Clause 6.3.2.3 Paragraph 1
    A pointer to void may be converted to or from a pointer to any object type. A pointer to any object type may be converted to a pointer to void and back again; the result shall compare equal to the original pointer.
    Quote Originally Posted by bubuche
    You are not talking about any running time problem.
    This might be due to some confusion when translating to English, but actually, if such a mistake is made, it will probably manifest at run time as a segmentation fault. The library user won't normally be able to detect it at compile time since the conversion to void* would hide the mistake.

    Quote Originally Posted by bubuche
    And, actually : when you use opaque pointers, the binaries produced are the same as when you use void pointers. And casts between pointers most (if not all) of times are translated into ... nothing. They don't exist in the binary : an address is an address.
    Types are not preserved at runtime or in the binaries.
    You're programming in C, not in assembly or machine language, so "an address is an address" in order to dismiss the importance of pointer type is not an observation applicable to C.

    Quote Originally Posted by bubuche
    Well, except if somebody has the solution (which probably doesn't exist) I am looking for, I think there is no point in this discussion anymore.
    Yeah, as I concluded in my previous post, my assessment is that you are out of luck and so will have to seek a compromise instead of getting exactly what you're looking for. I personally would be inclined to just use the traditional opaque pointer idiom anyway since I think that it is not worth worrying about edge cases like these since you just need to do a bit more work to accommodate, and that's a compromise I'm willing to make to stick to what most people would know.
    Last edited by laserlight; 02-07-2019 at 09:00 PM.
    Quote Originally Posted by Bjarne Stroustrup (2000-10-14)
    I get maybe two dozen requests for help with some sort of programming or design problem every day. Most have more sense than to send me hundreds of lines of code. If they do, I ask them to find the smallest example that exhibits the problem and send me that. Mostly, they then find the error themselves. "Finding the smallest program that demonstrates the error" is a powerful debugging tool.
    Look up a C++ Reference and learn How To Ask Questions The Smart Way

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Type definition issues?
    By FlyingIsFun1217 in forum C++ Programming
    Replies: 1
    Last Post: 07-05-2010, 07:17 PM
  2. class definition declaring ifstream object
    By rogster001 in forum C++ Programming
    Replies: 4
    Last Post: 12-14-2009, 10:47 AM
  3. declaring a parameter type without naming it?
    By -EquinoX- in forum C++ Programming
    Replies: 5
    Last Post: 11-06-2008, 04:17 AM
  4. declaring data type
    By sarathius in forum C Programming
    Replies: 4
    Last Post: 04-17-2008, 04:54 AM
  5. declaring a new type
    By c programming in forum C Programming
    Replies: 2
    Last Post: 11-19-2002, 12:31 PM

Tags for this Thread