Getting current c file filename but without repeated code



  • I'm thinking about sth like this, which of course does not work:

    // generic header
    
    #ifndef TEST_INCLUDEFILENAME_H_INCLUDED
    #define TEST_INCLUDEFILENAME_H_INCLUDED
    
    static char myFilename[] = __FILE__;
    
    #endif // TEST_INCLUDEFILENAME_H_INCLUDED
    
    //main 
    
    #include <stdio.h>
    #include <stdlib.h>
    #include "test_IncludeFilename.h"
    
    int main()
    {
        printf("Hello world!\n");
        printf("This file: %s\n", myFilename);
    
        return 0;
    }
    

    Here, the code outputs the filename of the header file instead of the source file name. This is obviously a fundamental preprocessor limitation.
    The post is about possible macro tricks which work around this situation without having to write

    static char filename[] = __FILE__;
    

    in every source. I'm trying to avoid repeated string constants like in this case:

    //main
    
        printf("Instance1 of filename %s", __FILE__);
        printf("Instance2 of filename %s", __FILE__);
        printf("Instance3 of filename %s", __FILE__);
        printf("Instance4 of filename %s", __FILE__);
        printf("Instance5 of filename %s", __FILE__);
    

    This is of course not more than an academic example.
    However, when writing f.e Unit-Tests, the __FILE__ macro will
    bloat the code uneccessary with many filename string constants, which actually
    are the same. I'm writing unit tests in a restricted env (64k) and so i need
    the mem for other more important things than repeated filename strings.
    Furthermore,

    writing

    static char filename[] = __FILE__;
    

    in every source prevents the unit tests from automatisation.



  • // generic header
    
    #ifndef TEST_INCLUDEFILENAME_H_INCLUDED
    #define TEST_INCLUDEFILENAME_H_INCLUDED
    
    #define myFilename __FILE__
    
    #endif // TEST_INCLUDEFILENAME_H_INCLUDED
    

  • Mod

    Konzept:

    #include <utility>
    
    template <char... c>
    struct static_string {
        static constexpr char value[] = { c..., '\0' };
    };
    template <typename, typename>
    struct trim;
    template <char... c, std::size_t... I>
    struct trim<static_string<c...>, std::index_sequence<I...>>
    : static_string<static_string<c...>::value[I]...>
    {};
    template <char... c>
    using trim_t = trim<static_string<c...>, std::make_index_sequence<((c != '\0') + ...)>>;
    
    #define STR2CHARLIST0(x, i)   (i < sizeof(x) ? x[i] : '\0')
    #define STR2CHARLIST1(x, i)   STR2CHARLIST0(x, i), STR2CHARLIST0(x, i+1)
    #define STR2CHARLIST2(x, i)   STR2CHARLIST1(x, i), STR2CHARLIST1(x, i+2)
    #define STR2CHARLIST4(x, i)   STR2CHARLIST2(x, i), STR2CHARLIST2(x, i+4)
    #define STR2CHARLIST8(x, i)   STR2CHARLIST4(x, i), STR2CHARLIST4(x, i+8)
    #define STR2CHARLIST16(x, i)  STR2CHARLIST8(x, i), STR2CHARLIST8(x, i+16)
    #define STR2CHARLIST32(x, i)  STR2CHARLIST16(x, i), STR2CHARLIST16(x, i+32)
    #define STR2CHARLIST64(x, i)  STR2CHARLIST32(x, i), STR2CHARLIST32(x, i+64)
    #define STR2CHARLIST128(x, i) STR2CHARLIST64(x, i), STR2CHARLIST64(x, i+128)
    #define STR2CHARLIST(x) STR2CHARLIST128(x, 0)
    #define SS(x) trim_t<STR2CHARLIST(x)>::value
    
    #include <iostream>
    int main() {
        std::cout << (void*)SS(__FILE__) << '\n';
        std::cout << (void*)SS(__FILE__) << '\n';
        std::cout << (void*)SS(__FILE__) << '\n';
        std::cout << (void*)SS(__FILE__) << '\n';
        std::cout << (void*)SS(__FILE__) << '\n';
        std::cout << (void*)SS(__FILE__) << '\n';
    }
    


  • camper schrieb:

    Konzept:

    #include <utility>
    
    template <char... c>
    struct static_string {
        static constexpr char value[] = { c..., '\0' };
    };
    template <typename, typename>
    struct trim;
    template <char... c, std::size_t... I>
    struct trim<static_string<c...>, std::index_sequence<I...>>
    : static_string<static_string<c...>::value[I]...>
    {};
    template <char... c>
    using trim_t = trim<static_string<c...>, std::make_index_sequence<((c != '\0') + ...)>>;
    
    #define STR2CHARLIST0(x, i)   (i < sizeof(x) ? x[i] : '\0')
    #define STR2CHARLIST1(x, i)   STR2CHARLIST0(x, i), STR2CHARLIST0(x, i+1)
    #define STR2CHARLIST2(x, i)   STR2CHARLIST1(x, i), STR2CHARLIST1(x, i+2)
    #define STR2CHARLIST4(x, i)   STR2CHARLIST2(x, i), STR2CHARLIST2(x, i+4)
    #define STR2CHARLIST8(x, i)   STR2CHARLIST4(x, i), STR2CHARLIST4(x, i+8)
    #define STR2CHARLIST16(x, i)  STR2CHARLIST8(x, i), STR2CHARLIST8(x, i+16)
    #define STR2CHARLIST32(x, i)  STR2CHARLIST16(x, i), STR2CHARLIST16(x, i+32)
    #define STR2CHARLIST64(x, i)  STR2CHARLIST32(x, i), STR2CHARLIST32(x, i+64)
    #define STR2CHARLIST128(x, i) STR2CHARLIST64(x, i), STR2CHARLIST64(x, i+128)
    #define STR2CHARLIST(x) STR2CHARLIST128(x, 0)
    #define SS(x) trim_t<STR2CHARLIST(x)>::value
    
    #include <iostream>
    int main() {
        std::cout << (void*)SS(__FILE__) << '\n';
        std::cout << (void*)SS(__FILE__) << '\n';
        std::cout << (void*)SS(__FILE__) << '\n';
        std::cout << (void*)SS(__FILE__) << '\n';
        std::cout << (void*)SS(__FILE__) << '\n';
        std::cout << (void*)SS(__FILE__) << '\n';
    }
    

    Könntest du mir das bitte kurz erklären? 🤡



  • Erstmal vielen Dank für die Antwort und eine Entschuldigung, dass ich in Englisch gepostet habe. Man denkt beim Programmieren irgendwie immer an Englisch.
    Die Lösung sieht recht kompliziert aus, daher muss ich nochmal nachfragen wie sie genau funktioniert. So ganz habe ich sie noch nicht verstanden.

    static_string setzt aus einem compile time char array einen const string zusammen, soviel kapier ich noch. Dann wird irgendwie trim mit dem I. Zeichen aus static_string initialisiert und ein Alias darauf definiert. Dann hört mein Verständnis auf.
    Sorry, kannst du nochmal für Dumme?

    Allein mit Mitteln des Präprozessors scheint es also keine Lösung zu geben, oder?
    Der Vorschlag von mcHammer ist zwar richtig, löst aber das grundlegende Problem nicht.


  • Mod

    STR2LITERAL wandelt übergebenen string in eine Liste von chars um,
    "abc" wird zu "abc"[0],"abs"[1], "abc"[2], "abc"[3], '\0', '\0' ...
    Weil wir innerhalb des Makros nicht abhängig vob der Länge sein können, wird hinten mit '\0' bis zu einer Maximallänge (hier 256) aufgefüllt.

    static_string<c...>::value ist ein Objekt, dass sich wie ein Stringliteral mit den einzelnen Zeichen c... verhält - dieses Objekt existiert aber nur einmal für eine bestimmte Zeichenfolge c...
    trim_t<c...>::value enthält den String c..., der um die überflüssigen '\0' am Ende gekürzt wurde.

    ((c != '\0') + ...) ist die Anzahl der Zeichen, die nicht '\0' sind.

    std::make_index_sequence<N> erstellt eine Liste 0,1,...,N-1
    in trim wird dann auf die ersten N Zeichen der ursprünglich zu langen char-Liste zugegriffen.

    Rendbedingungen sind also:
    1. Das Makroargument darf keine eingebetteten \0 - Zeichen enthalten
    2. Es funktioniert nur bis zu einer bestimmten Maximallänge (hier 256)


  • Mod

    Wenn Compilererweiterungen zugelassen sind, kann man auch so etwas machen

    #include <cstddef>
    template <typename T, T... c>
    struct static_string {
        static constexpr T value[] = { c..., T{} };
    };
    template <typename T, T... c> // GNU extension
    constexpr auto operator""_cstr() noexcept {
        return static_string<T, c...>{};
    }
    
    #define S_(x) x##_cstr.value
    #define SS(x) S_(x)
    
    #include <iostream>
    int main() {
        std::cout << &SS(__FILE__) << ' ' << SS(__FILE__) << '\n';
        std::cout << &SS(__FILE__) << ' ' << SS(__FILE__) << '\n';
    }
    

    Das ist dann effizient und ohne besondere Einschränkungen hinsichtlich des Inhalts des Stringliterals. Kommt vielleicht im Standard in 2050


  • Mod

    Nein, es kommt voraussichtlich gar nicht, weil die Darstellung von parameter packs in Implementierungen einfach ineffizient ist (wie Du ganz genau weißt, weil Du doch immer auf die Ineffizienz von rekursiven variadischen Templates verweist 😉 ). EWG ist nicht daran interessiert, solche Vorgehensweisen zu unterstützen, daher verweisen sie auf constexpr String Klassen. In nicht allzu ferner Zukunft können wir mit P0732R0 rechnen, und einfach einen entsprechenden Konstruktor schreiben, der ein Member-Array mit einem String-Literal initialisiert.


  • Mod

    Arcoth schrieb:

    Nein, es kommt voraussichtlich gar nicht, weil die Darstellung von parameter packs in Implementierungen einfach ineffizient ist (wie Du ganz genau weißt, weil Du doch immer auf die Ineffizienz von rekursiven variadischen Templates verweist 😉 ). EWG ist nicht daran interessiert, solche Vorgehensweisen zu unterstützen, daher verweisen sie auf constexpr String Klassen. In nicht allzu ferner Zukunft können wir mit P0732R0 rechnen, und einfach einen entsprechenden Konstruktor schreiben, der ein Member-Array mit einem String-Literal initialisiert.

    Dann ist also davon auszugehen, dass literal operator templates in ihrer jetzigen Form deprecated werden? Das wäre konsequent.


Log in to reply