C++ 是否可以在编译时执行字符串到int的映射?

C++ 是否可以在编译时执行字符串到int的映射?,c++,C++,是否可以在编译时执行唯一的字符串到int的映射? 假设我有这样一个用于分析的模板: template <int profilingID> class Profile{ public: Profile(){ /* start timer */ } ~Profile(){ /* stop timer */ } }; 模板 班级简介{ 公众: Profile(){/*启动计时器*/} ~Profile(){/*停止计时器*/} }; 我将其放在函数调用的开头,如下所示:

是否可以在编译时执行唯一的字符串到int的映射? 假设我有这样一个用于分析的模板:

template <int profilingID>
class Profile{
public:
    Profile(){ /* start timer */ }
    ~Profile(){ /* stop timer */ }
};
模板
班级简介{
公众:
Profile(){/*启动计时器*/}
~Profile(){/*停止计时器*/}
};
我将其放在函数调用的开头,如下所示:

void myFunction(){
    Profile<0> profile_me;

    /* some computations here */
}
void
function()
{
  const Profile<wrap("function")> profiler {};
}

int
main()
{
  const Profile<wrap("main")> profiler {};
  function();
}
void myFunction(){
个人资料;
/*这里有一些计算*/
}
现在我试着做如下的事情:

void myFunction(){
Profile Profile_me;//或Profile(“myFunction”)
/*这里有一些计算*/
}
我可以声明全局变量来克服这个问题,但我认为避免以前的声明会更优雅。表单的简单映射

  • “myFunction”→ 0
  • “myFunction1”→ 一,
  • “myFunctionN”→ N

那就足够了。但到目前为止,无论是使用constexpr、模板元编程还是宏,我都无法找到实现这种映射的方法。有什么想法吗?

原则上你可以。然而,我怀疑任何选择是否可行


您可以将密钥类型设置为
constepr
值类型(这不包括
std::string
),初始化实现的值类型也不是问题,只需从字符数组中插入一个
constepr
构造函数即可。但是,您还需要实现
constepr
map或哈希表,以及
constepr
哈希函数。实现
constexpr
map是困难的部分。仍然可行。

您可以创建一个表:

struct Int_String_Entry
{
  unsigned int id;
  char *       text;
};

static const Int_String_Entry my_table[] =
{
  {0, "My_Function"},
  {1, "My_Function1"},
  //...
};
const unsigned int my_table_size =
    sizeof(my_table) / sizeof(my_table[0]);
可能您需要的是一个带有函数指针的查找表

typedef void (*Function_Pointer)(void);
struct Int_vs_FP_Entry
{
  unsigned int func_id;
  Function_Point p_func;
};

static const Int_vs_FP_Entry func_table[] =
{
  { 0, My_Function},
  { 1, My_Function1},
  //...
};
为了更加完整,您可以将所有三个属性组合到另一个结构中,并创建另一个表


注意:由于表被声明为“static const”,因此它们是在编译时组装的

这是一个有趣的问题

可以按如下方式静态初始化std::map:

static const std::map<int, int> my_map {{1, 2}, {3, 4}, {5, 6}};
#include <cstdlib>
#include <iostream>
#include <map>
#include <chrono>
#include <cmath>

using ProfileMapping = std::map<std::string, std::size_t>;

ProfileMapping& Map() {
  static ProfileMapping map;
  return map;
}

void show_profiles() {
  for(const auto & pair : Map()) {
    std::cout << pair.first << " : " << pair.second << std::endl;
  }
}

class AutoProfiler {
 public:
  AutoProfiler(std::string name)
      : m_name(std::move(name)),
        m_beg(std::chrono::high_resolution_clock::now()) { }
  ~AutoProfiler() {
    auto end = std::chrono::high_resolution_clock::now();
    auto dur = std::chrono::duration_cast<std::chrono::milliseconds>(end - m_beg);
    Map().emplace(m_name, dur.count());
  }
 private:
  std::string m_name;
  std::chrono::time_point<std::chrono::high_resolution_clock> m_beg;
};

void foo() {
  AutoProfiler ap("foo");
  long double x {1};
  for(std::size_t k = 0; k < 1000000; ++k) {
    x += std::sqrt(k);
  }
}

void bar() {
  AutoProfiler ap("bar");
  long double x {1};
  for(std::size_t k = 0; k < 10000; ++k) {
    x += std::sqrt(k);
  }
}

void baz() {
  AutoProfiler ap("baz");
  long double x {1};
  for(std::size_t k = 0; k < 100000000; ++k) {
    x += std::sqrt(k);
  }
}

int main() {
  std::atexit(show_profiles);

  foo();
  bar();
  baz();

}
我将其汇编为:

$ g++ AutoProfile.cpp -std=c++14 -Wall -Wextra
并获得:

$ ./a.out
bar : 0
baz : 738
foo : 7
您不需要
-std=c++14
,但至少需要
-std=c++11

我知道这不是你想要的,但我喜欢你的问题,决定投入我的0.02美元

请注意,如果使用以下定义:

using ProfileMapping = std::multi_map<std::string, std::size_t>;
使用ProfileMapping=std::multi_map;
您可以记录对每个函数的每次访问(而不是在写入第一个条目后丢弃新结果,或覆盖旧结果)。

如注释中所述,您可能应该将名称传递给构造函数。这也可能有助于减少代码膨胀,因为您不会为每个函数生成新类型

#include <climits>
#include <cstdint>
#include <cstdio>
#include <type_traits>

template <typename T = std::uintmax_t>
constexpr std::enable_if_t<std::is_integral<T>::value, T>
wrap(const char *const string) noexcept
{
  constexpr auto N = sizeof(T);
  T n {};
  std::size_t i {};
  while (string[i] && i < N)
    n = (n << CHAR_BIT) | string[i++];
  return (n << (N - i) * CHAR_BIT);
}

template <typename T>
std::enable_if_t<std::is_integral<T>::value>
unwrap(const T n, char *const buffer) noexcept
{
  constexpr auto N = sizeof(T);
  constexpr auto lastbyte = static_cast<char>(~0);
  for (std::size_t i = 0UL; i < N; ++i)
    buffer[i] = ((n >> (N - i - 1) * CHAR_BIT) & lastbyte);
  buffer[N] = '\0';
}

template <std::uintmax_t Id>
struct Profile
{
  char name[sizeof(std::uintmax_t) + 1];

  Profile()
  {
    unwrap(Id, name);
    std::printf("%-8s %s\n", "ENTER", name);
  }

  ~Profile()
  {
    std::printf("%-8s %s\n", "EXIT", name);
  }
};
但是,我不想错过展示一个肮脏的hack的机会,它在字符串无法传递给构造函数的情况下可能很有用。如果字符串的最大长度在编译时已知,则可以将其编码为整数。在下面的示例中,我只使用一个整数,它将系统上的最大字符串长度限制为8个字符。将该方法扩展到多个整数(拆分逻辑被一个小宏方便地隐藏)留给读者作为练习

代码利用C++14功能在
constexpr
函数中使用任意控制结构。在C++11中,您必须将
wrap
编写为一个稍微不那么直接的递归函数

#include <climits>
#include <cstdint>
#include <cstdio>
#include <type_traits>

template <typename T = std::uintmax_t>
constexpr std::enable_if_t<std::is_integral<T>::value, T>
wrap(const char *const string) noexcept
{
  constexpr auto N = sizeof(T);
  T n {};
  std::size_t i {};
  while (string[i] && i < N)
    n = (n << CHAR_BIT) | string[i++];
  return (n << (N - i) * CHAR_BIT);
}

template <typename T>
std::enable_if_t<std::is_integral<T>::value>
unwrap(const T n, char *const buffer) noexcept
{
  constexpr auto N = sizeof(T);
  constexpr auto lastbyte = static_cast<char>(~0);
  for (std::size_t i = 0UL; i < N; ++i)
    buffer[i] = ((n >> (N - i - 1) * CHAR_BIT) & lastbyte);
  buffer[N] = '\0';
}

template <std::uintmax_t Id>
struct Profile
{
  char name[sizeof(std::uintmax_t) + 1];

  Profile()
  {
    unwrap(Id, name);
    std::printf("%-8s %s\n", "ENTER", name);
  }

  ~Profile()
  {
    std::printf("%-8s %s\n", "EXIT", name);
  }
};

为什么不使用以下枚举:

enum ProfileID{myFunction = 0,myFunction1 = 1, myFunction2 = 2 };
?


您的字符串将不会在运行时加载,因此我不理解在此处使用字符串的原因。

您可以执行以下类似操作。这有点尴尬,但可能比映射到整数更直接一些:

#include <iostream>

template <const char *name>
class Profile{
public:
    Profile() {
        std::cout << "start: " << name << std::endl;
    }
    ~Profile() {
        std::cout << "stop: " << name << std::endl;
    }
};


constexpr const char myFunction1Name[] = "myFunction1";

void myFunction1(){
    Profile<myFunction1Name> profile_me;

    /* some computations here */
}

int main()
{
    myFunction1();
}
#包括
模板
班级简介{
公众:
简介(){

STD::你可以使用预处理器技巧,或者有一些(例如C++头)。生成时生成的文件。为生成时生成而附议。我真的不明白为什么要在这里首先使用模板。与其为要分析的每个函数创建一个类,不如创建一个实例,将概要文件ID名称作为构造函数参数传入?或者是否有其他模板参数/speciali正在进行的分解?一个
enum
映射还不够吗?类似于:
enum{myFunction,myFunction1,…,myFunctionN}如果字符串都像
myFunction n
一样简单,只需去掉
myFunction
部分并将剩余部分转换为int,这将是我希望避免的附加声明之一。另外,我不想污染全局namespace。这将是我想要避免的附加声明之一。谢谢你的回答。但这些仍然是我想要避免的声明。不是为了性能,而是为了美观。正如你所说,这并不完全是我想要的。但是你的回答非常有趣。它确实避免了这种附加声明我在问题中提到的所有声明。如果没有映射查找开销,它将是完美的。请注意,任何映射查找都不会影响计时。构造函数首先构建字符串,然后才记录开始时间(ergo字符串构造不是计时的一部分)。类似地,析构函数首先记录结束时间,然后(在记录结束时间后)才计算持续时间并插入到映射中:计时不会受到任何映射开销的影响。