770 likes | 899 Views
ConceptClang : An Implementation Model for C++ Concepts – An Update. Larisse Voufo Center for Research in Extreme Scale Technologies (CREST) Indiana University Adrian Kothman , Maxwell Crouse CS Undergraduate Mentees PL-Wonks, November 2013. Topics. Generic programming with concepts
E N D
ConceptClang: An Implementation Model for C++ Concepts – An Update Larisse Voufo Center for Research in Extreme Scale Technologies (CREST) Indiana University Adrian Kothman, Maxwell Crouse CS Undergraduate Mentees PL-Wonks, November 2013
Topics • Generic programming with concepts • in C++. • Implementing concepts with ConceptClang • for C++, • in Clang. • in other compilers? • for other languages? • e.g. Chapel? • Talk to Max and Adrian.
Topics • Generic programming with concepts • in C++. • Implementing concepts with ConceptClang • for C++, • for other languages? • e.g. Chapel? • Ongoing theoretical findings: • Name binding framework: • Clarifies complex rules like argument-dependent lookup (ADL) in C++. • Introduces a new scoping rule: weak hiding. • Specifies a new mechanism: two-stage name binding. • Open classes (extensible structures) for free.
Outline • Brief Introduction: • Generic programming with concepts. • Constrained C++ templates. • Implementing concepts w/ ConceptClang. • Ongoing theoretical findings: • Name binding framework. • Open classes (extensible structures) for free. • ConceptClang’edChapel, an ongoing undergraduate project. • https://github.iu.edu/lvoufo/CCedChapel[branch:mvcrouse]
Generic Programming • Different languages support it in various capacities: Genericity by … • Value: e.g. function abstraction • Type: e.g. (parametric or adhoc) polymorphism • Function: e.g. functions as values • Structure: e.g. requirements and operations on types • Property: e.g. properties on type • Stage: e.g. meta-programming • Shape: e.g. datatype-generic cf. "Datatype Generic Programming”. Gibbons. [In Spring School on Datatype-Generic Programming, volume 4719 of Lecture Notes in Computer Science. Springer-Verlag.] • C++ supports generic programming via templates.
Generic Programming w/ Concepts • Different languages support it in various capacities: Genericity by … • Value: e.g. function abstraction • Type: e.g. (parametric or adhoc) polymorphism • Function: e.g. functions as values • Structure: e.g. requirements and operations on types • Property: e.g. properties on type • Stage: e.g. meta-programming • Shape: e.g. datatype-generic cf. "Datatype Generic Programming”. Gibbons. [In Spring School on Datatype-Generic Programming, volume 4719 of Lecture Notes in Computer Science. Springer-Verlag.] • C++ supports generic programming via constrained templates.
Example: Lifting template<InputIterator I, typenameT, BinaryFunction Op> requires(Assignable<InputIterator<I>::value_type, BinaryFunction<Op>::result_type>) Taccumulate(I first, I last, Tinit, Opbin_op) { for (; first != last; ++first) init = bin_op(init, *first); return init; } accumulate(arr, arr+3, 0, 1) int sum(int* array, int n) { int s = 0; for (inti = 0; i < n; ++i) s = s + array[i]; return s; } Same Complexity. sum(arr,3)
Example: Requirements Grouping concept InputIterator<typename X> : Iterator<X>,EqualityComparable<X>{ ObjectTypevalue_type = typename X::value_type; MoveConstructible pointer = typename X::pointer; SignedIntegralLikedifference_type = typename X::difference_type; ... pointer operator->(const X&); };
C++ Templates • C++ supports generic programming via templates. • Templates alone are not expressive enough, nor safe: • Too general. • Lengthy and obscure error messages. • Encapsulation breakage. • Primarily used by experts. • “Tricks” are even more complex to understand. • Worse: semantic errors go undetected.
Example 1: Error Detection and Diagnosis with C++ Templates vector<void*> v; sort(v.begin(), v.end(), boost::bind(less<int>(),_1,_2)); $ clang++ test.cpp -o example /usr/local/include/boost/bind/bind.hpp:303:16: error: no matching function for call to object of type 'std::less<int>' return unwrapper<F>::unwrap(f, 0)(a[base_type::a1_], a[base_type::a2_]); ^~~~~~~~~~~~~~~~~~~~~~~~~~ /usr/local/include/boost/bind/bind_template.hpp:61:27: note: in instantiation of function template specialization 'boost::_bi::list2<boost::arg<1>, boost::arg<2> >::operator()<bool, std::less<int>, boost::_bi::list2<void *&, void *&> >' requested here BOOST_BIND_RETURN l_(type<result_type>(), f_, a, 0); ^ /usr/include/c++/4.2.1/bits/stl_algo.h:2501:6: note: in instantiation of function template specialization 'boost::_bi::bind_t<boost::_bi::unspecified, std::less<int>, boost::_bi::list2<boost::arg<1>, boost::arg<2> > >::operator()<void *, void *>' requested here if (__comp(*__i, *__first)) ^ /usr/include/c++/4.2.1/bits/stl_algo.h:2591:7: note: in instantiation of function template ... 2 errors generated. Incompatible Binary Operator!
Example 2: Error Detection and Diagnosis with C++ Templates vector<int> v; sort(v.begin(), v.end(), not_equal_to<int>()); $ clang++ test.cpp -o example $ (None !?) Not Valid Ordering!
Example 1: Error Detection and Diagnosis with Constrained Templates vector<void*> v; constrained_sort(v.begin(), v.end(), boost::bind(less<int>(),_1,_2)); $ clang++ test.cpp -o example test.cpp:260:2: error: no matching function for call to 'constrained_sort' constrained_sort(v.begin(), v.end(), boost::bind(less<int>(), _1, _2)); ^~~~~~~~~~~~~~~~ ./constrained_algo.h:39:6: note: candidate template ignored: constraints check failure [with I = __gnu_cxx::__normal_iterator<void **, std::vector<void *, std::allocator<void *> > >, Cmp= boost::_bi::bind_t<boost::_bi::unspecified, std::less<int>, boost::_bi::list2<boost::arg<1>, boost::arg<2> > >] void constrained_sort(I first, I last, Cmpbin_op) { ^ ./constrained_algo.h:38:17: note: Concept map requirement not met. Assignable<RandomAccessIterator<I>::value_type, ... ^ ./constrained_algo.h:37:3: note: Constraints Check Failed: constrained_sort. requires(RandomAccessIterator<I>, StrictWeakOrdering<Cmp>, ^ 1 error generated.
Example 2: Error Detection and Diagnosis with Constrained Templates vector<int> v; constrained_sort(v.begin(), v.end(), not_equal_to<int>()); $ clang++ test.cpp -o example test.cpp:261:2: error: no matching function for call to 'constrained_sort' constrained_sort(v.begin(), v.end(), not_equal_to<int>()); ^~~~~~~~~~~~~~~~ ./constrained_algo.h:39:6: note: candidate template ignored: constraints check failure [with I = __gnu_cxx::__normal_iterator<int *, std::vector<int, std::allocator<int> > >, Cmp = std::not_equal_to<int>] void constrained_sort(I first, I last, Cmpbin_op) { ^ ./constrained_algo.h:37:55: note: Concept map requirement not met. requires(RandomAccessIterator<I>, StrictWeakOrdering<Cmp>, ^ ./constrained_algo.h:37:3: note: Constraints Check Failed: constrained_sort. requires(RandomAccessIterator<I>, StrictWeakOrdering<Cmp>, ^ 1 error generated.
Templates: Compiler Mechanism PARSING INSTANTIATION int accumulate (vector<int>::iterator first, vector<int>::iterator last, int init, plus<int> bin_op) { ...} template<typename Iter, typename T, typename BinOp> T accumulate(…) { …} Check Once! vector<int> v; int i = accumulate(v.begin(), v.end(), 0, plus<int>()); vector<int> v; int i = accumulate<vector<int>::iterator, int, plus<int> > (v.begin(), v.end(), 0, plus<int>()); int accumulate(vector<int>::iterator first, vector<int>::iterator last, int init, plus<int> bin_op); Template Definition: Template Use: Code Generation: Specialization:
Constrained Templates: Mechanism PARSING INSTANTIATION int accumulate (vector<int>::iterator first, vector<int>::iterator last, int init, plus<int> bin_op) { ...} template<typenameI, typenameT, typename BinOp> requires(InputIterator<I>, BinaryFunction<Op>, ...) T accumulate(...) { ... } Check Constraints-Check Once! vector<int> v; int i = accumulate<vector<int>::iterator, int, plus<int> > (v.begin(), v.end(), 0, plus<int>()); int accumulate(vector<int>::iterator first, vector<int>::iterator last, int init, plus<int> bin_op); InputIterator<vector<int>::iterator>, BinaryFunction<bin_op>, ... Constrained Template Definition: Constrained Template Use: Code Generation: Specialization + Models:
Concepts: Elementary Components • Concept Definition: • Name + parameters • Requirements • Refinements • Extends requirements • Constrained Template Definition: • Constraints specification • Concept Model (Template): • Concept id: name + arguments • Requirement satisfactions • Refinement satisfactions • One for each refinement • Constrained Template Use: • Constraints satisfaction
ConceptClang extensions affect only 4/~17 components of Clang: The Driver component is modified only for compiler flags support. So, we do not consider it in our analysis. ConceptClang Infrastructure
ConceptClang extensions affect only 4/~17 components of Clang: The Driver component is modified only for compiler flags support. So, we do not consider it in our analysis. ConceptClang Infrastructure See C++Now’12 Talk: “ConceptClang: Towards an Implementation Models for C++ Concepts”
Preliminary Observation • The ConceptClang infrastructure is parameterized by the (type of) associated requirements. • E.g. declarations in PF, expressions in PA, • Statements in Chapel… • Main workload is at the infrastructure layer, in Sema: • constraints satisfaction, • concept model lookup, checking, generating, • name rebinding at instantiation time. • So far, only simple name uses, e.g., function calls, are covered. • In PF, thus also simple associated declarations and types.
Current Limitations • In constrained generic components: Type-checking name uses weak hiding and Two-Stage Name Binding. • Beyond simple function calls: Generalizing name uses ( or Two-Stage Name Binding) structure-opening archetypes Open/Extensible classes/structures for free.
Outline • Brief Introduction: • Generic programming with concepts. • Constrained C++ templates. • Implementing concepts w/ ConceptClang. • Ongoing theoretical findings: • Name binding framework. • Open classes (extensible structures) for free. • ConceptClang’edChapel, an ongoing undergraduate project. • https://github.iu.edu/lvoufo/CCedChapel [branch:mvcrouse]
Current Design Limitations • No good solution, presently. • Our name binding framework explains. :: concept Foo2<typename P> = requires (P a, P b) { foo(a, b); } void foo(int) { } template<Foo2T> void gen_func(T a, T b) { foo(a, b); foo(1); } Should Foo2<T>::foo() shadow ::foo()? How? Foo2<T> Reject or accept the call foo(1)?
Our Name Binding Framework • Specifies name binding. :: void foo(); namespace adl { struct X {}; void foo(X); } namespace ns { void foo(int); } void test(adl::X x) { using ns::foo; foo(x); } adl ns test • Best viable candidate, or • error Bind
Our Name Binding Framework • Specifies name binding, independently of the language. • Identify and combine scopes. :: void foo(); namespace adl { struct X {}; void foo(X); } namespace ns { void foo(int); } void test(adl::X x) { using ns::foo; foo(x); } • Execute generic name binding. adl ns test • Best viable candidate, or • error Bind
Our Name Binding Framework • Specifies name binding, independently of the language. • Express scoping rules. :: void foo(); namespace adl { struct X {}; void foo(X); } namespace ns { void foo(int); } void test(adl::X x) { using ns::foo; foo(x); } • Apply scoping rules. adl ns test • Best viable candidate, or • error Bind
Our Name Binding Framework • Specifies name binding, independently of the language. Use scope combinators. :: void foo(); namespace adl { struct X {}; void foo(X); } namespace ns { void foo(int); } void test(adl::X x) { using ns::foo; foo(x); } • Identify name binding on an elementary scope (generic). adl ns test • Best viable candidate, or • error Bind
Our Name Binding Framework • Specifies name binding, independently of the language. Use scope combinators. :: void foo(); namespace adl { struct X {}; void foo(X); } namespace ns { void foo(int); } void test(adl::X x) { using ns::foo; foo(x); } • Depends on a Language concept. adl ns test • Best viable candidate, or • error Bind
Our Name Binding Framework • Specifies name binding, independently of the language. Use scope combinators. :: void foo(); namespace adl { struct X {}; void foo(X); } namespace ns { void foo(int); } void test(adl::X x) { using ns::foo; foo(x); } • Instantiate the Languageconcept. adl ns test • Best viable candidate, or • error Bind
Our Name Binding Framework • Abstracts from declarations, references, and scopes. void foo(); namespace adl { struct X {}; void foo(X); } namespace ns { void foo(int); } void test(adl::X x) { using ns::foo; foo(x); } • Scopes as maps of references to sets of matching declarations. • Best viable candidate, or • error Bind
Our Name Binding Framework • Views name binding as composed of name lookup and resolution. • Name lookup returns the set of matching declarations, for a given reference. void foo(); namespace adl { struct X {}; void foo(X); } namespace ns { void foo(int); } void test(adl::X x) { using ns::foo; foo(x); } • Best viable candidate, or • error Bind
Expressing Scoping Rules Use scope combinators. :: void foo(); namespace adl { struct X {}; void foo(X); } namespace ns { void foo(int); } void test(adl::X x) { using ns::foo; foo(x); } • Instantiate the Languageconcept. adl ns test • Best viable candidate, or • error Bind
The Combinators • Hiding: • Commonly known as “shadowing”. • Merging: • Usuallythe alternative option to “shadowing”. • Opening: • [New name] Necessary to describe ADL. • A dual of hiding. • Weak Hiding: • [New rule] Necessary for (C++) concepts. • A sweet middle between hiding and merging.
The HidingCombinator() void foo(); void test() { foo(); } :: Result: Binds foo() to ::foo(). test
The MergingCombinator( ) void foo(); namespace ns{ void foo(int); } void test() { using ns::foo; foo(); } :: ns Result: Finds ns::foo(); Fails to bind foo(). test
The Weak HidingCombinator( ) void foo(); namespace ns{ void foo(int); } void test() { using ns::foo; foo(); } :: ns Result: Binds foo() to ::foo(). test
The OpeningCombinator() void foo(); namespace ns{ void foo(int); } namespace adl { struct X{}; void foo(typ); } void test(adl::Xx) { using ns::foo; foo(x); } :: ns Result: Finds ns::foo(); Enables ADL; Binds foo(x)to adl::foo(). adl test
The OpeningCombinator() void foo(); namespace ns { void foo(int); } namespace adl{ struct X {}; void foo(typ); } void test(adl::Xx) { using ns::foo; void foo(); foo(x); } :: ns Result: Finds test::foo(); Disables ADL; Fails to bind foo(x). adl test
Applications • Understanding current name binding mechanisms: • Argument-dependent lookup (ADL). • C++ operators. • A cross-language analysis. • Exploring concepts designs • Understanding the current limitations. • Exploring new solutions: • weak hiding, • 2-Stage name binding, and • parameterized weak hiding. • Simplifying compiler designs.
C++ Operators Example structX{}; structY {}; void operator+(X, X) { } void operator+(X, Y) { } void test(X x, Y y) { void operator+(X, X); x + x; x + y; operator+(x, x); operator+(x, y); } test ::
C++ Operators Scoping Ruleswith ADL Empty, since operator is a reserved keyword.
Problem: Current Limitations • Current scoping rules break seemingly valid codes. :: concept Foo2<typename P> = requires (P a, P b) { foo(a, b); } void foo(int) { } template<Foo2T> void gen_func(T a, T b) { foo(a, b); foo(1); } Should Foo2<T>::foo() shadow ::foo()? How? Foo2<T> Reject or accept the call foo(1)?
Solution: Weak Hiding :: concept Foo2<typename P> = requires (P a, P b) { foo(a, b); } void foo(int) { } template<Foo2T> void gen_func(T a, T b) { foo(a, b); foo(1); } Foo2<T>::foo() should weakly hide ::foo()! Foo2<T> Accept the call foo(1)!
The Weak Hiding Scoping Rule concept Foo2<typename P> = requires (P a, P b) { …} void foo(int) { } template<Foo2T> void gen_func(T a, T b) { foo(a, b); foo(1); } Result: Binds foo() to ::foo(). Concept<T> ::
Implementing Weak Hiding • Implementation = Two-Stage Name Binding (Bindx2) • Bind with inner scope: s1. • Bind with outer scope:s2. • Bindx2 repeats name binding under different contexts.
Bindx2 for C++ Concepts • Within restricted scope: • up to the outermost restricted scope. • Disables ADL and some qualified name lookups. • In surrounding scope: • normal lookup – including ADL. concept Foo2<typename P> = requires (P a, P b) { …} void foo(int) { } template<Foo2T> void gen_func(T a, T b) { foo(a, b); foo(1); } Foo2<T> ::
Ambiguity for C++ Concepts:What is the most desirable? Rejects desirable, or binds to undesirable • Ambiguity IS an error, always? • Ambiguity IS NOT an error, always? • A middle ground option? Accepts undesirable concept Foo2<typename P> { void foo(P, int); void foo(int, P); } template<Foo2 T> void gen_func(T a, int b) { foo(b, b);foo(b); } Proposed Extension: Accept only desirable! (?)
Ambiguity for C++ Concepts:What is the most desirable? Rejects desirable, or binds to undesirable • Ambiguity IS an error, always? • Ambiguity IS NOT an error, always? • A middle ground option? See C++Now’13 Talk: “Weak Hiding for C++ Concepts and a Generic Way to Understand Name Binding.” Accepts undesirable concept Foo2<typename P> { void foo(P, int); void foo(int, P); } template<Foo2 T> void gen_func(T a, int b) { foo(b, b);foo(b); } Proposed Extension: Accept only desirable! (?)