Jak (o ile możliwe) obliczyć wartość wyrażenia w czasie kompilacji?

0

Czy jest jakiś odpowiednik, bloku inicjalizacyjnego (statycznego bądź instancyjnego) z Javy, który działby podobnie w C++? Prosty przykład. W Javie jeśli w jakiejś klasie chcę mieć stałą, której wartością jest powiedzmy suma dodatnich kwadratów od 0 do 10 to pisze się to tak:

class Foo {
    static final int SQUARE;
    
    static {
        int sumOfSquare = 0;
        for(int i = 1; i <= 10; i++) {
            sumOfSquare += i * i;
        }
        SQUARE = sumOfSquare;
    }
}

W C++ mamy constexpr, dzięki któremu możemy obliczać wartości stałe w czasie kompilacji, a nawet obliczać takie stałe korzystając z funkcji (które jednak również muszą być zadeklarowane jako constexpr). No i pomału zbliżamy się do rozwiązania problemu ale jest jedno ale.
Załóżmy, że w odpowiedniej klasie ale już w C++ chcę mieć to samo co powyżej w Javie. Mogę to napisać tak:

constexpr int computeSquare(int n) {
    int sumOfSquare = 0;
    for(int i = 1; i <= 10; i++) {
        sumOfSquare += i * i;
    }
    return sumOfSquare;
}

class Foo {
    static constexpr int SQUARE = computeSquare(10);
};

No i jest super, bo zostało to obliczone w czasie kompilacji. To co rozróżnia jednak ten przykład od powyższego w Javie jest to, że ja cały czas mam dostępną funkcję constexpr int computeSquare(int n), którą mogę wywoływać. A tego nie chcę. Widzę dwa rozwiązania:

  1. Mogę zostawić to tak jak jest, godząc się na większy kod wynikowy właśnie o tę funkcję.
  2. Mogę to obliczyć wcześniej na kartce i wstawić gotową obliczoną wartość i w ogóle nie korzystać z constexpr ale to był tylko przykład i przy bardziej skomplikowanym wyrażeniu obliczenia na kartce mogą potrwać wieki.

Więc pytanie jest takie: jak obliczyć w czasie kompilacji (o ile się da) skomplikowane wyrażenie i przypisać to do stałej ale tak, żeby nie mieć później dostępnej funkcji dzięki któremu obliczyło się owo stałą wartość? Czy można zrobić coś podobnego jak blok inicjalizacyjny w Javie?

1

A co takiego złego jest w pozostawieniu tej funkcji dla runtime'u? Jak nie będzie wywołana to nie będzie jej w kodzie i tak.

W C++20 prawdopodobnie będzie is_constant_evaluated(), więc będziesz mógł postawić static_asserta w jednym branchu. Ale problem uważam za sztuczny.

https://en.cppreference.com/w/cpp/types/is_constant_evaluated

2

Od wersji C++17 możliwe jest użycie lambdy.

struct Foo1 {
    static constexpr int SQUARE =  [](int a) constexpr 
                                                       {  
                                                         int sumOfSquare = 0; 
                                                         for( int i=1 ; i <= a ; ++i ) { sumOfSquare += i*i; } 
                                                         return sumOfSquare; 
                                                       }(10);
};
2

Przejmujesz się jakimś nieistotnym detalem.
Jeśli funkcja nie jest eksportowana z dll-ki ani nie jest używana to linker ją po prostu wywali (w kodzie wynikowym jej nie będzie).
Zresztą inker jest w tym aż tak dobry, że czasami jest zbyt łapczywy i "self registration" przez to nie działa: https://stackoverflow.com/a/56583926/1387438

1

W C++ można użyć metaprogramowania. Chwila w google i z tego typu obliczeniami:
https://gist.github.com/MikimotoH/282dca62e08b90b9b673
Wpisanie wyników do kodu na sztywno nie jest złym pomysłem, jeśli używamy takich danych bardzo często. Powiedzmy, że mamy baaardzo złożony obliczeniowo algorytm "tabliczka mnożenia". Liczenie pierwszych stu elementów może potrwać dwa dni. Nikt nie lubi czekać kilku dni, żeby moduł się kompilował. Wtedy pierwsze sto elementów można wyliczyć i stworzyć tablicę stałych.

2

Akurat to co w Javie napisałeś można sprywatyzować w C++ w module implementacji (*.cpp):
https://stackoverflow.com/a/185863

Jeśli o jakieś bardziej problemy szablonowe to możesz zastosować pattern z Boosta (namespace details):

#include <iostream>
#include <string>

namespace details {
    template<int num> 
    class computeSquare {
        public: enum { value = num * num + computeSquare<num - 1>::value }; 
    };
    
    template<> 
    class computeSquare<1> {
        public: enum { value = 1 }; 
    };
    
    template<> 
    class computeSquare<0> {
        public: enum { value = 0 }; 
    };
};

class Foo {
 private:
 public: 
    enum { SQUARE  = details::computeSquare<10>::value };
};

int main()
{
  std::string name;
  std::cout << "Square: " << Foo::SQUARE;
}
0

Dzięki wszystkim. To jednak zostawię to z constexpr skoro kompilator nie wkleja tej funkcji do kodu jeśli nie jest nigdzie indziej wykorzystywana. Zapomniałem zupełnie o tym aspekcie.
Rozwiązanie z templateami - bardzo ciekawe. Ogarnę to później :)

1 użytkowników online, w tym zalogowanych: 0, gości: 1