Могу ли я обнаружить во время компиляции "аргументы функции", которые являются константами времени компиляции

Можно ли во время компиляции определить, являются ли "аргументы функции" 1 константами времени компиляции?

Например, функция print(int i), которая может печатать "constant 5", если она называется print(5), но "non-constant 5", если она называется print(i), где i - некоторая непостоянная переменная. В частности, в ветке "is constant" я должен иметь возможность рассматривать i как constexpr, включая его использование для аргументов шаблона и т.д.

Макро-трюки, мета-программирование шаблонов и трюки SFINAE все в порядке. В идеале это портативный, но решения, специфичные для компилятора, лучше, чем ничего.

Хорошо, если есть "ложные негативы" - то есть, если постоянные значения иногда обнаруживаются как непостоянные (например, когда определенные оптимизации отключены).

Бонусные точки, если решение может обнаружить, когда постоянные значения косвенно передаются функции (например, когда постоянное значение передается промежуточной функции, которая вызывает print и которая впоследствии встроена, подвергая константу print), Это последнее поведение, очевидно, зависит от оптимизации.

Двойные бонусные очки, если он естественно распространяется на несколько аргументов.

Если бы вы могли перегружать версии функций с аргументами constexpr и без них, это, вероятно, было бы просто, но вы не можете.


1 Я помещаю "аргументы функции" в кавычки здесь, потому что решение строго не требует обнаружения этого состояния внутри функции (или на границе вызывающего/вызываемого со специальными аргументами) - это просто должен появиться вызывающему, как функция, но могут быть использованы макросы или другие трюки, такие как статический объект с operator() и т.д.

Ответ 1

он не должен быть простой функцией void print (int i) - он может быть функционально подобным макросом, который делает некоторые магические аргументы и вызывает другую функцию в зависимости от того, является ли она константой или она может быть магия шаблона

"функционально-подобный макрос", вы сказали?

Хорошо... Прежде всего, я должен предупредить вас, что макрос функций типа C-типа опасен. Дистиллированное зло, ИМХО.

Сказано, что если вы действительно принимаете решение на основе макросов, я полагаю, что объединение его с методами constexpr, локальными переменными struct, static и SFINAE...

Если вы определяете следующий шаблон PrintStruct struct

template <typename T>
struct PrintStruct
 {
   template <bool>
   static void func (...) 
    { std::cout << "func non-const: " << T::func(true) << std::endl; }

   template <bool b, int I = T::func(b)>
   static void func (int) 
    { std::cout << "func const:     " << I << std::endl; }
 };

и следующий макрос функций типа C, которые определяют foo local struct и передают его в качестве аргумента шаблона в PrintStruct для активации SFINAE для выбора желаемого func() (и вызова func(), очевидно) [ EDIT: макрос улучшен с помощью jxh, чтобы он расширялся как оператор; спасибо!] [ EDIT 2: макрос изменен, после наблюдения OP, чтобы принимать выражения]

#define Print(i)                          \
[&]()                                     \
 {                                        \
   static int const printLocalVar { i };  \
                                          \
   struct local_foo                       \
    {                                     \
      static constexpr int func (bool b)  \
       { return b ? printLocalVar : 0; }  \
    } ;                                   \
                                          \
   PrintStruct<local_foo>::func<true>(0); \
 }                                        \
()

Обратите внимание, что напечатанное значение в версии const PrintStruct::func() является целым числом шаблона; поэтому могут использоваться также для аргументов шаблона, размеров массива C-стиля, тестов static_assert() и т.д.

Не уверен, что это абсолютно стандартно (я не очень эксперт) и делаю то, что вы точно хотите, но ниже приведен полный рабочий пример

#include <iostream>

template <typename T>
struct PrintStruct
 {
   template <bool>
   static void func (...) 
    { std::cout << "func non-const: " << T::func(true) << std::endl; }

   template <bool b, int I = T::func(b)>
   static void func (int) 
    { std::cout << "func const:     " << I << std::endl; }
 };


#define Print(i)                          \
[&]()                                     \
 {                                        \
   static int const printLocalVar { i };  \
                                          \
   struct local_foo                       \
    {                                     \
      static constexpr int func (bool b)  \
       { return b ? printLocalVar : 0; }  \
    } ;                                   \
                                          \
   PrintStruct<local_foo>::func<true>(0); \
 }                                        \
()

int main()
 {
   constexpr int  i { 2 };
   int const      j { 3 };
   int            k { 4 };
   int const      l { k+1 };

   Print(1);    // print func const:     1
   Print(i);    // print func const:     2
   Print(j);    // print func const:     3
   Print(k);    // print func non-const: 4
   Print(l);    // print func non-const: 5
   Print(2+2);  // print func const:     4
 }

Ответ 2

Для определения соответствия constexpr можно рассмотреть это предложение GCC от @JohannesSchaub-litb (см. связанный ответ для ограничений ):

template<typename T> 
constexpr typename remove_reference<T>::type makeprval(T && t) {
  return t;
}

Рабочий пример с разными случаями читает

#include <iostream>

////////////////////////////////////////////////////////////////////////////////

// /info/150781/is-isconstexpr-possible-in-c11/879477#879477

template<class T>
constexpr std::remove_reference_t<T> makeprval(T&& t) {
  return t;
}

#define isprvalconstexpr(e) noexcept(makeprval(e))

////////////////////////////////////////////////////////////////////////////////

template<bool is_constexpr, class Lambda>
struct HybridArg {
  using T = std::invoke_result_t<Lambda>;

  Lambda lambda_;
  constexpr operator T() const { return lambda_(); }// implicit conversion
};

template<bool is_constexpr, class Lambda>
constexpr auto make_hybrid_arg(Lambda lambda) {
  return HybridArg<is_constexpr, Lambda>{lambda};
}

#define WRAP_ARG(arg)                     \
  make_hybrid_arg<isprvalconstexpr(arg)>( \
    [&] { return arg; }                   \
  )                                       \

////////////////////////////////////////////////////////////////////////////////

template<int i>
void print_impl_constexpr() {
  std::cout << i << ": ";
  std::cout << __PRETTY_FUNCTION__ << std::endl;
}

void print_impl_fallback(int i) {
  std::cout << i << ": ";
  std::cout << __PRETTY_FUNCTION__ << std::endl;
}

////////////////////////////////////////////////////////////////////////////////

// option 1 (for choosing implementation):
// compile-time introspection

template<class Arg>
struct is_constexpr_arg : std::false_type {};

template<class Lambda>
struct is_constexpr_arg<
  HybridArg<true, Lambda>
> : std::true_type {};

template<class Arg>
void print_by_introspection(Arg arg) {
  if constexpr(is_constexpr_arg<Arg>{}) {
    print_impl_constexpr<arg>();
  }
  else {
    print_impl_fallback(arg);
  }
}

////////////////////////////////////////////////////////////////////////////////

// option 2 (for choosing implementation):
// overload

void print_by_overload(int arg) {
  print_impl_fallback(arg);
}

template<class Lambda>
void print_by_overload(HybridArg<true, Lambda> arg) {
  print_impl_constexpr<arg>();
}

////////////////////////////////////////////////////////////////////////////////

template<class Arg>
void indirection(Arg arg) {
  print_by_introspection(arg);
  print_by_overload(arg);
}

void bad_indirection(int arg) {
  print_by_introspection(arg);
  print_by_overload(arg);
}

////////////////////////////////////////////////////////////////////////////////

int main() {
  {
    int i = 0;
    indirection(i);
  }
  {
    int i = 1;
    indirection(WRAP_ARG(i));
  }
  {
    constexpr int i = 2;
    indirection(WRAP_ARG(i));
  }
  {
    constexpr int i = 3;
    bad_indirection(WRAP_ARG(i));
  }
}

Выход после компиляции с помощью GCC:

0: void print_impl_fallback(int)
0: void print_impl_fallback(int)
1: void print_impl_fallback(int)
1: void print_impl_fallback(int)
2: void print_impl_constexpr() [with int i = 2]
2: void print_impl_constexpr() [with int i = 2]
3: void print_impl_fallback(int)
3: void print_impl_fallback(int)