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

时间:2015-06-30 23:59:35

标签: c++

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

template <int profilingID>
class Profile{
public:
    Profile(){ /* start timer */ }
    ~Profile(){ /* stop timer */ }
};

我放在函数调用的开头,如下所示:

void myFunction(){
    Profile<0> profile_me;

    /* some computations here */
}

现在我尝试执行以下操作,which is not possible since string literals cannot be used as a template argument

void myFunction(){
    Profile<"myFunction"> profile_me; // or PROFILE("myFunction")

    /* some computations here */
}

我可以声明全局变量来克服这个问题,但我认为避免以前的声明会更优雅。表单的简单映射

  • “myFunction”→0
  • “myFunction1”→1
  • ...
  • “myFunctionN”→N

就足够了。但到目前为止,既不使用constexpr,模板元编程也不使用宏,我可以找到实现这种映射的方法。有什么想法吗?

6 个答案:

答案 0 :(得分:0)

原则上你可以。但是,我怀疑任何选择都是实用的。

您可以将密钥类型设置为constexpr值类型(这不包括std::string),初始化您实现的值类型也不是问题,只需在其中输入{{1}来自一组字符的构造函数。但是,您还需要实现constexpr映射,哈希表和constexpr哈希函数。实施constexpr地图是困难的部分。仍然可行。

答案 1 :(得分:0)

您可以创建一个表格:

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”,因此它们在编译期间被汇编。

答案 2 :(得分:0)

这是一个有趣的问题。

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

static const std::map<int, int> my_map {{1, 2}, {3, 4}, {5, 6}};

但是我知道这样的初始化并不是你想要的,所以我在看了你的例子后采取了另一种方法。

全局注册表包含函数名称(std::string)和运行时间之间的映射(表示毫秒数的std::size_t)。

构造AutoProfiler,提供函数的名称,它将记录当前时间。在销毁时(将在我们退出函数时发生),它将计算经过的时间并将其记录在全局注册表中。

当程序结束时,我们打印地图的内容(为此我们使用std::atexit功能)。

代码如下:

#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>;

您可以记录对每个功能的每次访问权限(而不是在写完第一个条目后弃用新结果,或覆盖旧结果)。

答案 3 :(得分:0)

正如@harmic已在评论中提到过,您可能只需将名称传递给构造函数即可。这也可能有助于减少代码膨胀,因为您不会为每个函数生成新类型。

但是,我不想错过在无法将字符串传递给构造函数的情况下显示可能有用的脏黑客的机会。如果您的字符串具有编译时已知的最大长度,则可以将它们编码为整数。在下面的示例中,我只使用一个整数,它将系统上的最大字符串长度限制为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);
  }
};

可以像这样使用:

void
function()
{
  const Profile<wrap("function")> profiler {};
}

int
main()
{
  const Profile<wrap("main")> profiler {};
  function();
}

输出:

ENTER    main
ENTER    function
EXIT     function
EXIT     main

答案 4 :(得分:0)

为什么不直接使用Enum:

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

您的字符串不会在运行时加载,所以我不明白在这里使用字符串的原因。

答案 5 :(得分:0)

你可以做类似以下的事情。它有点尴尬,但可能比映射到整数更直接地做你想做的事情:

#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();
}