C++ 是否可以在编译时执行字符串到int的映射?
是否可以在编译时执行唯一的字符串到int的映射? 假设我有这样一个用于分析的模板:C++ 是否可以在编译时执行字符串到int的映射?,c++,C++,是否可以在编译时执行唯一的字符串到int的映射? 假设我有这样一个用于分析的模板: template <int profilingID> class Profile{ public: Profile(){ /* start timer */ } ~Profile(){ /* stop timer */ } }; 模板 班级简介{ 公众: Profile(){/*启动计时器*/} ~Profile(){/*停止计时器*/} }; 我将其放在函数调用的开头,如下所示:
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字符串构造不是计时的一部分)。类似地,析构函数首先记录结束时间,然后(在记录结束时间后)才计算持续时间并插入到映射中:计时不会受到任何映射开销的影响。