constexpr 문자열 리터럴 검사 : 짧은 구문, 런타임 가능성 없음
편집 : 내 최종 솔루션이 중독 방법을 사용하지 않기 때문에 이름이 변경되었습니다.
런타임에 constexpr 메서드가 호출되는 것을 방지하는 방법을 찾고 있습니다. 문자열 리터럴을 받아들이는 함수를 작성 중이므로 constexpr
매개 변수 를 요구하는 방법으로 NTTP를 간단히 사용할 수는 없습니다 .
template<const char* str>
auto func() {...}
합법적 인 constexpr 사용조차도 번거롭기 때문에 값에 정적 연결이 필요하고 문자열 리터럴을 공급할 수 없습니다. 나하고 싶어:
constexpr auto func(const char* str) {...}
그 이유는 값 목록에 대해 문자열을 확인하고 매개 변수가 허용 된 집합에 포함되어 있는지 정적으로 확인하기 때문입니다. 나는 이것을 쉽게 할 수있다. 단지 함수에 throw()
'ing in a constexpr
function, you can cause a compile-time error. 그러나 프로그램이 런타임에 종료되도록하는 일부 분기로 프로덕션 코드를 생성 할 가능성을 원하지 않습니다. 그것은 내 분야에서 큰 문제를 일으킬 것입니다. 이 기능은 "기타 중요한 작업"을 수행하는 프로그램에서 유용하며 프로그램이 종료되면 나쁜 일이 발생합니다.
이 작업을 수행 할 수있는 여러 가지 가능한 방법에 대해 읽었습니다.
- C ++ 20 사용-C ++ 20
consteval
없음 - C ++ 20 사용-C ++ 20
std::is_constant_evaluated
없음 - 정의되지 않은 기호 (예 : 정의되지 않은 기호)로 결과를 반환하여 런타임에 메서드를 독살하십시오
extern int i
. 컴파일러는 메서드가 컴파일 타임에 호출되는 경우 해당 기호를 반환하는 코드를 생성하지 않지만 런타임에 메서드가 호출되면 링크 오류가 발생합니다. 작동하지만 못생긴 링커 오류; 내가 제일 좋아하는 게 아니야. 그 버전은 여기 내 게시물에 나와 있습니다 : Constexpr functions not called at compile-time if result is ignore . - C ++ 17에서는 컨텍스트 에서 실제로
noexcept
호출되는constexpr
함수 에 대한 호출에 자동으로 추가constexpr
됩니다. 따라서 호출이 / not이 되는지 여부를 감지하기 위해noexcept(foo(1))
vsnoexcept(foo(i))
forconstexpr int foo(int i)
(명시 적으로 선언되지 않음noexcept
)를 수행 할i
수 있습니다constexpr
. 그러나constexpr
일부 매개 변수를 수락 한 함수 내에서는이를 수행 할 수 없습니다 . 호출 사이트에서 수행해야합니다. 그래서 : 아마도 도우미 매크로가 필요합니다 (내가 좋아하는 것은 아니지만 작동합니다). - 람다 범위 밖의 특정 변수가 아닌 경우 반환 유형이 잘못된 람다를 만듭니다
constexpr
. 이 게시물은 자세히 설명합니다.https://stackoverflow.com/a/40413051
그래서 저는 # 3 또는 # 4 + 매크로를 사용하는쪽으로 기울고 있지만 ***이 게시물은 # 5에 관한 것입니다 *** 또는 완전히 새로운 아이디어입니다.
예를 들어 누구나 람다없이 # 5를 할 수있는 방법을 생각 해낼 수 있습니까? 그 후 constexpr
호출 사이트에서 사용하도록 요구하지 않고 함수 자체 내에서 사용하는 방법을 찾을 수 있는지 확인하고 싶습니다 . 지금 constexpr
은 런타임에 호출 되는 함수 를 독살하려고 시도 하고 함수 호출이인지 여부를 "감지"하는 것은 잊어 버리십시오 constexpr
.
main
저자가했던 것처럼 람다를 생성하여 # 5의 결과를 재현 할 수 있지만 실제로는 그다지 유용하지 않으며 여전히 완전히 합법적이라고 확신하지 못합니다. 우선 람다로 할 수있는 것은 람다없이 할 수 있습니다. 맞죠 ??? 람다 없이는 원래 저자의 방법을 사용할 수도 없습니다. 외부에서 작동하도록하는 데 필요한 첫 번째 단계 인 것 같습니다 main()
.
아래는 람다없이 # 5를 재현하려고 시도한 몇 가지 아이디어입니다. 10 억 개 이상의 순열이있는 실제 예는 작동하지 않습니다.https://onlinegdb.com/B1oRjpTGP
// Common
template<int>
using Void = void;
// Common
struct Delayer {
constexpr auto delayStatic(int input) { return input; }
};
// Attempt 1
template<typename PoisonDelayer>
constexpr auto procurePoison(int i) {
struct Poison {
// error: use of parameter from containing function
// constexpr auto operator()() const -> Void<(PoisonDelayer::delayStatic(i), 0)> {}
} poison;
return poison;
}
// Attempt 2
struct PoisonInnerTemplate {
const int& _i;
// Internal compiler error / use of this in a constexpr
template<typename PoisonDelayer>
auto drink() const -> Void<(PoisonDelayer::delayStatic(_i), 0)> {}
};
int main()
{
auto attempt1 = procurePoison<Delayer>(1);
constexpr int i = 1;
auto attempt2 = PoisonInnerTemplate{i};
attempt2.drink<Delayer>();
return 0;
}
한 가지 더 : 허용 된 태그의 미리 정의 된 목록을 만들고 (문자열을 NTTP가 될 수 있도록 구조체로 래핑) 일종의 컨테이너에 넣은 다음 검색 할 메서드를 갖는 아이디어를 가지고 놀았습니다. 그들. 문제는 다음과 같습니다. (1) 호출 사이트 구문이 사용하기에 매우 장황합니다. (2) 호출 사이트가와 같은 구문을 사용하는 것이 좋지만 MyTags::TAG_ONE
내 프로그램이 전체 집합을 알 수 있어야합니다. 태그의 수이므로 배열 또는 템플릿 변수에 있어야합니다. (3) 배열을 사용하는 것은 작동하지 않습니다. 배열 요소를 가져 rvalue
오면 연결이없는을 생성 하므로 a로 공급할 수 없습니다. NTTP, (4) 명시 적 전문화가있는 템플릿 변수를 사용하여 각 태그를 정의하려면 템플릿 변수가 전역 범위 여야하는데, 이는 저에게 적합하지 않습니다.
이것은 내가 할 수있는 최선에 대한 것입니다.
struct Tag {
const char* name;
};
template<auto& tag>
void foo() {}
struct Tags {
static constexpr Tag invalid = {};
static constexpr Tag tags[] = {{"abc"}, {"def"}};
template<size_t N>
static constexpr Tag tag = tags[N];
template<size_t N = 0>
static constexpr auto& getTag(const char* name) {
if constexpr(N<2) {
if(string_view(name)==tag<N>.name) {
return tag<N>;
} else {
return getTag<N+1>(name);
}
} else {
return invalid;
}
}
};
int main()
{
foo<Tags::getTag("abc")>();
}
답변
다음은 문자열 리터럴이 COMPILE-TIME에 허용 된 집합 내에 있는지 확인한 다음 해당 문자열의 값에 따라 작업을 수행하는 내 답변입니다. constexpr
함수의 중독 이 필요하지 않으며 정적 연결이있는 문자열 리터럴을 제공하기위한 번거로운 요구 사항도 여전히 없습니다.
gcc
C ++ 20의 일부이지만 C ++ 17이 아닌 문자열 템플릿 사용자 정의 리터럴에 대한 확장을 사용하는 "축약 형 옵션 2"에 대한 크레딧은 Jarod42로 이동합니다 .
세 가지 "속기"호출 사이트 구문 중 하나에 만족한다고 생각합니다. 나는 여전히 어떤 대안이나 개선점 또는 내가 엉망으로 만든 것에 대한 조언을 환영합니다. 퍼펙트 포워딩 등은 독자를위한 연습 문제로 남겨집니다 ;-)
라이브 데모 : https://onlinegdb.com/S1K_7sb7D
// Helper for Shorthand Option 1 (below)
template<typename Singleton>
Singleton* singleton;
// Helper to store string literals at compile-time
template<typename ParentDispatcher>
struct Tag {
using Parent = ParentDispatcher;
const char* name;
};
// ---------------------------------
// DISPATCHER:
// ---------------------------------
// Call different functions at compile-time based upon
// a compile-time string literal.
// ---------------------------------
template<auto& nameArray, typename FuncTuple>
struct Dispatcher {
FuncTuple _funcs;
using DispatcherTag = Tag<Dispatcher>;
template<size_t nameIndex>
static constexpr DispatcherTag TAG = {nameArray[nameIndex]};
static constexpr DispatcherTag INVALID_TAG = {};
Dispatcher(const FuncTuple& funcs) : _funcs(funcs) {
singleton<Dispatcher> = this;
}
template<size_t nameIndex = 0>
static constexpr auto& tag(string_view name) {
if(name == nameArray[nameIndex]) {
return TAG<nameIndex>;
} else {
if constexpr (nameIndex+1 < nameArray.size()) {
return tag<nameIndex+1>(name);
} else {
return INVALID_TAG;
}
}
}
static constexpr size_t index(string_view name) {
for(size_t nameIndex = 0; nameIndex < nameArray.size(); ++nameIndex) {
if(name == nameArray[nameIndex]) {
return nameIndex;
}
}
return nameArray.size();
}
constexpr auto& operator()(const char* name) const {
return tag(name);
}
template<auto& tag, typename... Args>
auto call(Args... args) const {
static constexpr size_t INDEX = index(tag.name);
static constexpr bool VALID = INDEX != nameArray.size();
static_assert(VALID, "Invalid tag.");
return get<INDEX*VALID>(_funcs)(args...);
}
};
template<auto& nameArray, typename FuncTuple>
auto makeDispatcher(const FuncTuple& funcs) {
return Dispatcher<nameArray, FuncTuple>(funcs);
}
// ---------------------------------
// SHORTHAND: OPTION 1
// ---------------------------------
// Use a singleton pattern and a helper to let a tag be associated with a
// specific dispatcher, so that the call-site need not specify dispatcher twice
// ---------------------------------
template<auto& tag, typename... Args>
auto call(Args... args) {
using Tag = remove_reference_t<decltype(tag)>;
using ParentDispatcher = typename Tag::Parent;
static auto dispatcher = singleton<ParentDispatcher>;
return dispatcher->template call<tag>(args...);
}
// ---------------------------------
// SHORTHAND: OPTION 2
// ---------------------------------
// Use a string template user-defined literal operator to shorten call-site syntax
// gcc supports this as an extension implementing proposal N3599 (standardized in C++20)
// If warnings occur, try pragma GCC diagnostic ignored "-Wgnu-string-literal-operator-template"
// ---------------------------------
// Need characters to be in contiguous memory on the stack (not NTTPs) for TAG_FROM_LITERAL
template<char... name>
constexpr char NAME_FROM_LITERAL[] = {name..., '\0'};
// Don't need to specify Dispatcher with user-defined literal method; will use dispatcher.check<>()
struct TagFromLiteral {};
// Need to have a constexpr variable with linkage to use with dispatcher.check<>()
template<char... name>
constexpr Tag<TagFromLiteral> TAG_FROM_LITERAL = {NAME_FROM_LITERAL<name...>};
// Create a constexpr variable with linkage for use with dispatcher.check<>(), via "MyTag"_TAG
template<typename Char, Char... name>
constexpr auto& operator"" _TAG() {
return TAG_FROM_LITERAL<name...>;
}
// ---------------------------------
// SHORTHAND: OPTION 3
// ---------------------------------
// Use a macro so the call-site need not specify dispatcher twice
// ---------------------------------
#define DISPATCH(dispatcher, name) dispatcher.call<dispatcher(name)>
// ---------------------------------
// COMMON: TEST FUNCTIONS
// ---------------------------------
bool testFunc1(int) { cout << "testFunc1" << endl; }
bool testFunc2(float) { cout << "testFunc2" << endl; }
bool testFunc3(double) { cout << "testFunc3" << endl; }
static constexpr auto funcs = make_tuple(&testFunc1, &testFunc2, &testFunc3);
static constexpr auto names = array{"one", "two", "three"};
int main()
{
// Create a test dispatcher
auto dispatcher = makeDispatcher<names>(funcs);
// LONG-HAND: call syntax: a bit verbose, but operator() helps
dispatcher.call<dispatcher("one")>(1);
// SHORTHAND OPTION 1: non-member helper, singleton maps back to dispatcher
call<dispatcher("one")>(1);
// SHORTHAND OPTION 2: gcc extension for string UDL templates (C++20 standardizes this)
dispatcher.call<"one"_TAG>(1);
// SHORHAND OPTION 3: Macro
DISPATCH(dispatcher, "one")(1);
return 0;
}