Немного уличной магии, либо как статически определить вызывается ли функция
Недавно мне задали задачку, в обсуждения всё свелось к следующему: — есть объект, в нём есть методы. Каждый метод/ы требует загрузки какой-то логики в рантайме. Мы хотим точно знать — какие методы мы вызвали, после в рантайме затребовать загрузки только нужной функциональности.
Дисклеймер
Сразу предвосхищу множество комментов на тему «а вот в стандарте не определено», «а вот мой гцц 5», «а вот в моей команде си с классами» и прочее. Поэтому, если всё (что-либо из) это касается вас — ненужно применять ничего из описанного здесь в своей практике. Применять подобное могут те, кто понимает что он делает и какие у этого последствия.
Мои представления о C++ весьма специфичны и я никому не рекомендую без каких-либо оснований следовать тому, чему следую я. А так же прошу воздержаться от излишнего навязывания мне видений ваших.
Везде, где я говорю о С++, либо о каком-либо его поведении — я всегда имею ввиду gnu++, и поведение его (gnu++) реализаций. Если я буду писать дальше — это будет проявляться все больше и больше.
Ненужно приходить и демонстрировать свои познания бюрократии, разводя здесь религиозные войны.
Фокус базируется на нескольких базовых свойствах
struct a {
static inline auto x = 123;//статическая инициализация происходит
//до старта программы, которая main
};
struct b {
static bool f() {
return true;
}
static inline auto x = f();
};
Здесь static inline auto x = f()
— инициализация зависит от результата, значит в процессе необходимо вызвать функцию. Любые побочные эффекты в функции будут исполнены, даже несмотря на то, что там return true
— это базовая семантика языка.
таким образом подобная программа:
#include
struct c {
static bool f() {
fprintf(stderr, "%s\n", __PRETTY_FUNCTION__);
return true;
}
static inline auto x = f();
};
int main() {
fprintf(stderr, "%s\n", __PRETTY_FUNCTION__);
}
выведет:
static bool c::f()
int main()
Самый очевидный паттерн использования — это
template struct plugin {
static bool f() {
fprintf(stderr, "%s\n", __PRETTY_FUNCTION__);
return true;
}
static inline auto x = f();
};
struct my_plugin: plugin {};//подобное работать не будет
Правило здесь простое — любые сущности внутри полиморного/шаблонного контекста инстанцируются лениво, т.е. только при обращении.
template struct test {
auto f() {
T x = "";
}
};
test _;//никакой ошибки небудет.
Это позволяет не платить за то, что мы не используем
возьмём подобный пример:
template struct integral_constant {
constexpr operator auto() const { return x; }
template constexpr integral_constant operator%(integral_constant) const { return {};}
template constexpr integral_constant operator+(integral_constant) const { return {};}
};
static_assert(integral_constant<1.>{} + integral_constant<2.>{} == 3.);
— нам ненужно реализовывать то, что мы не используем. В данном случае %
Аналогично с остальным:
static_assert(integral_constant<3>{} % integral_constant<2>{} == 1);
constexpr auto test_mod(auto a, auto b) {
return requires {
a % b;
};
}
static_assert(!test_mod(integral_constant<1.>{}, integral_constant<2.>{}));
static_assert(test_mod(integral_constant<1>{}, integral_constant<2>{}));
Решение проблемы выше очевидно — нужно использовать поле вне полиморфного/шаблонного контекста
struct my_plugin2: plugin {
static inline auto x = plugin::x;//сайд-эффектом этой инициализации является вызов plugin::f.
//То, что нам и нужно
};
Осталось лишь совместить все вместе
template struct registry {
static auto push() {
fprintf(stderr, "%s\n", __PRETTY_FUNCTION__);
return true;
}
static inline auto x = push();
};
#undef NDEBUG
#include
template struct object {
void a() {
assert(registry<&object::a>::x);
}
void b() {
assert(registry<&object::b>::x);
}
};
void test_registry() {
object o;
o.a();//static auto registry< >::push() [with auto = &object::a]
//o.b();//static auto registry< >::push() [with auto = &object::b]
}
Так же мы можем воспользоваться свойством static_assert, который требует конвертации выражения в constexpr, но не требует всего выражения как constexpr
constexpr integral_constant true_;
template struct registryv2 {
static auto push() {
fprintf(stderr, "%s\n", __PRETTY_FUNCTION__);
return true_;
}
static inline auto x = push();
};
template struct objectv2 {
void a() {
static_assert(registryv2<&objectv2::a>::x);
}
void b() {
static_assert(registryv2<&objectv2::b>::x);
}
};
void test_registryv2() {
objectv2 o;
o.a();//static auto registryv2< >::push() [with auto = &objectv2::a]
// o.b();//static auto registryv2< >::push() [with auto = &objectv2::b]
}
template void f() {
static_assert(registryv2<&f>::x);
}
void test_f() {
// f();//static auto registryv2::push() [with auto tag = f<>]
}
int main() {
fprintf(stderr, "%s\n", __PRETTY_FUNCTION__);
}
Запретить сувать так просто в шаблон какие-то аргументы можно очень просто:
using private_unique_type = decltype([]{});
template void f2() {
static_assert(__is_same(T, private_unique_type));
static_assert(registryv2<&f2>::x);
}
void test_f2() {
// f2();
}
Полный текст, с которым можно поиграться.