DUNE PDELab (git)

basistest.hh
1// -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2// vi: set et ts=4 sw=2 sts=2:
3
4// SPDX-FileCopyrightText: Copyright © DUNE Project contributors, see file AUTHORS.md
5// SPDX-License-Identifier: LicenseRef-GPL-2.0-only-with-DUNE-exception OR LGPL-3.0-or-later
6
7#ifndef DUNE_FUNCTIONS_FUNCTIONSPACEBASES_TEST_BASISTEST_HH
8#define DUNE_FUNCTIONS_FUNCTIONSPACEBASES_TEST_BASISTEST_HH
9
10#include <set>
11#include <algorithm>
12#include <string>
13#include <sstream>
14#include <map>
15#include <optional>
16
17#include <dune/common/test/testsuite.hh>
20#include <dune/common/hybridutilities.hh>
21
23
24#include <dune/functions/functionspacebases/concepts.hh>
25#include <dune/functions/functionspacebases/test/enabledifferentiabilitycheck.hh>
26
27struct CheckBasisFlag {};
28struct AllowZeroBasisFunctions {};
29
30template<class T, class... S>
31struct IsContained : public std::disjunction<std::is_same<T,S>...>
32{};
33
34/*
35 * Get string identifier of element
36 */
37template<class Element, class GridView>
38std::string elementStr(const Element& element, const GridView& gridView)
39{
40 std::stringstream s;
41 s << element.type() << "#" << gridView.indexSet().index(element);
42 return s.str();
43}
44
45/*
46 * Check if two multi-indices are consecutive.
47 * This is a used by checkBasisIndexTreeConsistency()
48 */
49template<class MultiIndex>
50bool multiIndicesConsecutive(const MultiIndex& a, const MultiIndex& b)
51{
52 std::size_t i = 0;
53
54 // find largest common prefix
55 for (; (i<a.size()) and (i<b.size()) and (a[i] == b[i]); ++i)
56 {};
57
58 // if b is exhausted but a is not, then b is a strict prefix of a and does not succeed a
59 if ((i<a.size()) and (i==b.size()))
60 return false;
61
62 // if a and b are not exhausted, then the first non-common index must be an increment
63 if ((i<a.size()) and (i<b.size()))
64 {
65 if (b[i] != a[i]+1)
66 return false;
67 ++i;
68 }
69
70 // if b is not exhausted, then the following indices should be zero
71 if (i<b.size())
72 {
73 for (; i<b.size(); ++i)
74 {
75 if (b[i] != 0)
76 return false;
77 }
78 }
79 return true;
80}
81
82
83
84/*
85 * Check if given set of multi-indices is consistent, i.e.,
86 * if it induces a consistent ordered tree. This is used
87 * by checkBasisIndices()
88 */
89template<class MultiIndexSet>
90Dune::TestSuite checkBasisIndexTreeConsistency(const MultiIndexSet& multiIndexSet)
91{
92 Dune::TestSuite test("index tree consistency check");
93
94 using namespace Dune;
95
96 auto it = multiIndexSet.begin();
97 auto end = multiIndexSet.end();
98
99 // get first multi-index
100 auto lastMultiIndex = *it;
101
102 // assert that index is non-empty
103 test.require(lastMultiIndex.size()>0, "multi-index size check")
104 << "empty multi-index found";
105
106 // check if first multi-index is [0,...,0]
107 for (decltype(lastMultiIndex.size()) i = 0; i<lastMultiIndex.size(); ++i)
108 {
109 test.require(lastMultiIndex[i] == 0, "smallest index check")
110 << "smallest index contains non-zero entry " << lastMultiIndex[i] << " in position " << i;
111 }
112
113 ++it;
114 for(; it != end; ++it)
115 {
116 auto multiIndex = *it;
117
118 // assert that index is non-empty
119 test.require(multiIndex.size()>0, "multi-index size check")
120 << "empty multi-index found";
121
122 // assert that indices are consecutive
123 test.check(multiIndicesConsecutive(lastMultiIndex, multiIndex), "consecutive index check")
124 << "multi-indices " << lastMultiIndex << " and " << multiIndex << " are subsequent but not consecutive";
125
126 lastMultiIndex = multiIndex;
127 }
128
129 return test;
130}
131
132
133
134/*
135 * Check consistency of basis.size(prefix)
136 */
137template<class Basis, class MultiIndexSet>
138Dune::TestSuite checkBasisSizeConsistency(const Basis& basis, const MultiIndexSet& multiIndexSet)
139{
140 Dune::TestSuite test("index size consistency check");
141
142 // Based on the index tree, build a map that contains all possible
143 // prefixes and maps each prefix to the size (of the subsequent digit).
144 using Prefix = typename Basis::SizePrefix;
145 auto prefixSet = std::map<Prefix, std::size_t>();
146 for(const auto& index : multiIndexSet)
147 {
148 auto prefix = Prefix();
149 for (const auto& i: index)
150 {
151 prefixSet[prefix] = std::max(prefixSet[prefix], i+1);
152 prefix.push_back(i);
153 }
154 prefixSet[prefix] = 0;
155 }
156
157 // Now check for all prefixes, if the size computed from the
158 // index tree is consistent with basis.size(prefix).
159 for(const auto& [prefix, size] : prefixSet)
160 {
161 auto prefixSize = basis.size(prefix);
162 test.check(prefixSize == size, "basis.size(prefix) check")
163 << "basis.size(" << prefix << ")=" << prefixSize << ", but should be " << size;
164 }
165
166 return test;
167}
168
169
170
171/*
172 * Check indices of basis:
173 * - First store the whole index tree in a set
174 * - Check if this corresponds to a consistent index tree
175 * - Check if index tree is consistent with basis.size(prefix) and basis.dimension()
176 */
177template<class Basis>
178Dune::TestSuite checkBasisIndices(const Basis& basis)
179{
180 Dune::TestSuite test("basis index check");
181
182 using MultiIndex = typename Basis::MultiIndex;
183
184 static_assert(Dune::IsIndexable<MultiIndex>(), "MultiIndex must support operator[]");
185
186 auto compare = [](const auto& a, const auto& b) {
187 return std::lexicographical_compare(a.begin(), a.end(), b.begin(), b.end());
188 };
189
190 auto multiIndexSet = std::set<MultiIndex, decltype(compare)>{compare};
191
192 auto localView = basis.localView();
193 for (const auto& e : elements(basis.gridView()))
194 {
195 localView.bind(e);
196
197 for (decltype(localView.size()) i=0; i< localView.size(); ++i)
198 {
199 auto multiIndex = localView.index(i);
200 for(auto mi: multiIndex)
201 test.check(mi>=0)
202 << "Global multi-index contains negative entry for shape function " << i
203 << " in element " << elementStr(localView.element(), basis.gridView());
204 multiIndexSet.insert(multiIndex);
205 }
206 }
207
208 test.subTest(checkBasisIndexTreeConsistency(multiIndexSet));
209 test.subTest(checkBasisSizeConsistency(basis, multiIndexSet));
210 test.check(basis.dimension() == multiIndexSet.size())
211 << "basis.dimension() does not equal the total number of basis functions.";
212
213 return test;
214}
215
216
217
218/*
219 * Check if shape functions are not constant zero.
220 * This is called by checkLocalView().
221 */
222template<class LocalFiniteElement>
223Dune::TestSuite checkNonZeroShapeFunctions(const LocalFiniteElement& fe, std::size_t order = 5, double tol = 1e-10)
224{
225 Dune::TestSuite test;
226 static const int dimension = LocalFiniteElement::Traits::LocalBasisType::Traits::dimDomain;
227
228 auto quadRule = Dune::QuadratureRules<double, dimension>::rule(fe.type(), order);
229
230 std::vector<typename LocalFiniteElement::Traits::LocalBasisType::Traits::RangeType> values;
231 std::vector<bool> isNonZero;
232 isNonZero.resize(fe.size(), false);
233 for (const auto& qp : quadRule)
234 {
235 fe.localBasis().evaluateFunction(qp.position(), values);
236 for(std::size_t i=0; i<fe.size(); ++i)
237 isNonZero[i] = (isNonZero[i] or (values[i].infinity_norm() > tol));
238 }
239 for(std::size_t i=0; i<fe.size(); ++i)
240 test.check(isNonZero[i])
241 << "Found a constant zero basis function";
242 return test;
243}
244
245
246
247/*
248 * Check localView. This especially checks for
249 * consistency of local indices and local size.
250 */
251template<class Basis, class LocalView, class... Flags>
252Dune::TestSuite checkLocalView(const Basis& basis, const LocalView& localView, Flags... flags)
253{
254 Dune::TestSuite test(std::string("LocalView on ") + elementStr(localView.element(), basis.gridView()));
255
256 test.check(localView.size() <= localView.maxSize(), "localView.size() check")
257 << "localView.size() is " << localView.size() << " but localView.maxSize() is " << localView.maxSize();
258
259 // Count all local indices appearing in the tree.
260 std::vector<std::size_t> localIndices;
261 localIndices.resize(localView.size(), 0);
262 Dune::TypeTree::forEachLeafNode(localView.tree(), [&](const auto& node, auto&& treePath) {
263 test.check(node.size() == node.finiteElement().size())
264 << "Size of leaf node and finite element are different.";
265 for(std::size_t i=0; i<node.size(); ++i)
266 {
267 test.check(node.localIndex(i) < localView.size())
268 << "Local index exceeds localView.size().";
269 if (node.localIndex(i) < localView.size())
270 ++(localIndices[node.localIndex(i)]);
271 }
272 });
273
274 // Check if each local index appears exactly once.
275 for(std::size_t i=0; i<localView.size(); ++i)
276 {
277 if (localIndices[i])
278 test.check(localIndices[i]>=1)
279 << "Local index " << i << " did not appear";
280 test.check(localIndices[i]<=1)
281 << "Local index " << i << " appears multiple times";
282 }
283
284 // Check that all basis functions are non-zero.
285 if (not IsContained<AllowZeroBasisFunctions, Flags...>::value)
286 {
287 Dune::TypeTree::forEachLeafNode(localView.tree(), [&](const auto& node, auto&& treePath) {
288 test.subTest(checkNonZeroShapeFunctions(node.finiteElement()));
289 });
290 }
291
292 return test;
293}
294
295
296// Flag to enable a local continuity check for checking strong
297// continuity across an intersection within checkBasisContinuity().
298//
299// For each inside basis function this will compute the jump against
300// zero or the corresponding inside basis function. The latter is then
301// checked for being (up to a tolerance) zero on a set of quadrature points.
302struct EnableContinuityCheck
303{
304 std::size_t order_ = 5;
305 double tol_ = 1e-10;
306
307 template<class JumpEvaluator>
308 auto localJumpContinuityCheck(const JumpEvaluator& jumpEvaluator, std::size_t order, double tol) const
309 {
310 return [=](const auto& intersection, const auto& treePath, const auto& insideNode, const auto& outsideNode, const auto& insideToOutside) {
311 using Intersection = std::decay_t<decltype(intersection)>;
312 using Node = std::decay_t<decltype(insideNode)>;
313
314 std::vector<int> isContinuous(insideNode.size(), true);
315 const auto& quadRule = Dune::QuadratureRules<double, Intersection::mydimension>::rule(intersection.type(), order);
316
317 using Range = typename Node::FiniteElement::Traits::LocalBasisType::Traits::RangeType;
318 std::vector<std::vector<Range>> values;
319 std::vector<std::vector<Range>> neighborValues;
320
321 // Evaluate inside and outside basis functions.
322 values.resize(quadRule.size());
323 neighborValues.resize(quadRule.size());
324 for(std::size_t k=0; k<quadRule.size(); ++k)
325 {
326 auto pointInElement = intersection.geometryInInside().global(quadRule[k].position());
327 auto pointInNeighbor = intersection.geometryInOutside().global(quadRule[k].position());
328 insideNode.finiteElement().localBasis().evaluateFunction(pointInElement, values[k]);
329 outsideNode.finiteElement().localBasis().evaluateFunction(pointInNeighbor, neighborValues[k]);
330 }
331
332 // Check jump against outside basis function or zero.
333 for(std::size_t i=0; i<insideNode.size(); ++i)
334 {
335 for(std::size_t k=0; k<quadRule.size(); ++k)
336 {
337 auto jump = values[k][i];
338 if (insideToOutside[i].has_value())
339 jump -= neighborValues[k][insideToOutside[i].value()];
340 isContinuous[i] = isContinuous[i] and (jumpEvaluator(jump, intersection, quadRule[k].position()) < tol);
341 }
342 }
343 return isContinuous;
344 };
345 }
346
347 auto localContinuityCheck() const {
348 auto jumpNorm = [](auto&&jump, auto&& intersection, auto&& x) -> double {
349 return jump.infinity_norm();
350 };
351 return localJumpContinuityCheck(jumpNorm, order_, tol_);
352 }
353};
354
355// Flag to enable a local normal-continuity check for checking strong
356// continuity across an intersection within checkBasisContinuity().
357//
358// For each inside basis function this will compute the normal jump against
359// zero or the corresponding inside basis function. The latter is then
360// checked for being (up to a tolerance) zero on a set of quadrature points.
361struct EnableNormalContinuityCheck : public EnableContinuityCheck
362{
363 auto localContinuityCheck() const {
364 auto normalJump = [](auto&&jump, auto&& intersection, auto&& x) -> double {
365 return jump * intersection.unitOuterNormal(x);
366 };
367 return localJumpContinuityCheck(normalJump, order_, tol_);
368 }
369};
370
371// Flag to enable a local tangential-continuity check for checking continuity
372// of tangential parts of a vector-valued basis across an intersection
373// within checkBasisContinuity().
374//
375// For each inside basis function this will compute the tangential jump against
376// zero or the corresponding outside basis function. The jump is then
377// checked for being (up to a tolerance) zero on a set of quadrature points.
378struct EnableTangentialContinuityCheck : public EnableContinuityCheck
379{
380 auto localContinuityCheck() const {
381 auto tangentialJumpNorm = [](auto&&jump, auto&& intersection, auto&& x) -> double {
382 auto tangentialJump = jump - (jump * intersection.unitOuterNormal(x)) * intersection.unitOuterNormal(x);
383 return tangentialJump.two_norm();
384 };
385 return localJumpContinuityCheck(tangentialJumpNorm, order_, tol_);
386 }
387};
388
389// Flag to enable a center continuity check for checking continuity in the
390// center of an intersection within checkBasisContinuity().
391//
392// For each inside basis function this will compute the jump against
393// zero or the corresponding inside basis function. The latter is then
394// checked for being (up to a tolerance) zero in the center of mass
395// of the intersection.
396struct EnableCenterContinuityCheck : public EnableContinuityCheck
397{
398 template<class JumpEvaluator>
399 auto localJumpCenterContinuityCheck(const JumpEvaluator& jumpEvaluator, double tol) const
400 {
401 return [=](const auto& intersection, const auto& treePath, const auto& insideNode, const auto& outsideNode, const auto& insideToOutside) {
402 using Node = std::decay_t<decltype(insideNode)>;
403 using Range = typename Node::FiniteElement::Traits::LocalBasisType::Traits::RangeType;
404
405 std::vector<int> isContinuous(insideNode.size(), true);
406 std::vector<Range> insideValues;
407 std::vector<Range> outsideValues;
408
409 insideNode.finiteElement().localBasis().evaluateFunction(intersection.geometryInInside().center(), insideValues);
410 outsideNode.finiteElement().localBasis().evaluateFunction(intersection.geometryInOutside().center(), outsideValues);
411
412 auto centerLocal = intersection.geometry().local(intersection.geometry().center());
413
414 // Check jump against outside basis function or zero.
415 for(std::size_t i=0; i<insideNode.size(); ++i)
416 {
417 auto jump = insideValues[i];
418 if (insideToOutside[i].has_value())
419 jump -= outsideValues[insideToOutside[i].value()];
420 isContinuous[i] = isContinuous[i] and (jumpEvaluator(jump, intersection, centerLocal) < tol);
421 }
422 return isContinuous;
423 };
424 }
425
426 auto localContinuityCheck() const {
427 auto jumpNorm = [](auto&&jump, auto&& intersection, auto&& x) -> double {
428 return jump.infinity_norm();
429 };
430 return localJumpCenterContinuityCheck(jumpNorm, tol_);
431 }
432};
433
434 // Flag to enable a vertex continuity check for checking continuity at the vertices
435 // of an intersection within checkBasisContinuity().
436 //
437 // For each inside basis function this will compute the jump against
438 // zero or the corresponding inside basis function. The latter is then
439 // checked for being (up to a tolerance) zero in the vertices of mass
440 // of the intersection.
441
442 struct EnableVertexContinuityCheck: public EnableContinuityCheck
443 {
444 template <class JumpEvaluator>
445 auto localJumpVertexContinuityCheck(const JumpEvaluator &jumpEvaluator, double tol) const
446 {
447 return [=](const auto &intersection, const auto &treePath, const auto &insideNode,
448 const auto &outsideNode, const auto &insideToOutside)
449 {
450 using Node = std::decay_t<decltype(insideNode)>;
451 using Range = typename Node::FiniteElement::Traits::LocalBasisType::Traits::RangeType;
452
453 std::vector<int> isContinuous(insideNode.size(), true);
454 std::vector<Range> insideValues;
455 std::vector<Range> outsideValues;
456
457 std::vector<typename std::decay_t<decltype(intersection)>::LocalCoordinate> vertices;
458
459 for (int i = 0; i < intersection.geometry().corners(); ++i)
460 vertices.push_back(intersection.geometry().local(intersection.geometry().corner(i)));
461
462 for (auto const &vertex : vertices)
463 {
464
465 insideNode.finiteElement().localBasis().evaluateFunction(
466 intersection.geometryInInside().global(vertex), insideValues);
467 outsideNode.finiteElement().localBasis().evaluateFunction(
468 intersection.geometryInOutside().global(vertex), outsideValues);
469 // Check jump against outside basis function or zero.
470 for (std::size_t i = 0; i < insideNode.size(); ++i)
471 {
472 auto jump = insideValues[i];
473 if (insideToOutside[i].has_value())
474 jump -= outsideValues[insideToOutside[i].value()];
475 isContinuous[i] = isContinuous[i] and (jumpEvaluator(jump, intersection, vertex) < tol);
476 }
477 }
478 return isContinuous;
479 };
480 }
481
482 auto localContinuityCheck() const
483 {
484 auto jumpNorm = [](auto &&jump, auto &&intersection, auto &&x) -> double
485 { return jump.infinity_norm(); };
486 return localJumpVertexContinuityCheck(jumpNorm, tol_);
487 }
488 };
489
490/*
491 * Check if basis functions are continuous across faces.
492 * Continuity is checked by evaluation at a set of quadrature points
493 * from a quadrature rule of given order.
494 * If two basis functions (on neighboring elements) share the same
495 * global index, their values at the quadrature points (located on
496 * their intersection) should coincide up to the given tolerance.
497 *
498 * If a basis function only appears on one side of the intersection,
499 * it should be zero on the intersection.
500 */
501template<class Basis, class LocalCheck>
502Dune::TestSuite checkBasisContinuity(const Basis& basis, const LocalCheck& localCheck)
503{
504 Dune::TestSuite test("Global continuity check of basis functions");
505
506
507 auto localView = basis.localView();
508 auto neighborLocalView = basis.localView();
509
510 for (const auto& e : elements(basis.gridView()))
511 {
512 localView.bind(e);
513 for(const auto& intersection : intersections(basis.gridView(), e))
514 {
515 if (intersection.neighbor())
516 {
517 neighborLocalView.bind(intersection.outside());
518
519 Dune::TypeTree::forEachLeafNode(localView.tree(), [&](const auto& insideNode, auto&& treePath) {
520 const auto& outsideNode = Dune::TypeTree::child(neighborLocalView.tree(), treePath);
521
522 std::vector<std::optional<int>> insideToOutside;
523 insideToOutside.resize(insideNode.size());
524
525 // Map all inside DOFs to outside DOFs if possible
526 for(std::size_t i=0; i<insideNode.size(); ++i)
527 {
528 for(std::size_t j=0; j<outsideNode.size(); ++j)
529 {
530 if (localView.index(insideNode.localIndex(i)) == neighborLocalView.index(outsideNode.localIndex(j)))
531 {
532 // Basis function should only appear once in the neighbor element.
533 test.check(not insideToOutside[i].has_value())
534 << "Basis function " << localView.index(insideNode.localIndex(i))
535 << " appears twice in element " << elementStr(neighborLocalView.element(), basis.gridView());
536 insideToOutside[i] = j;
537 }
538 }
539 }
540
541 // Apply continuity check on given intersection with given inside/outside DOF node pair.
542 auto isContinuous = localCheck(intersection, treePath, insideNode, outsideNode, insideToOutside);
543
544 for(std::size_t i=0; i<insideNode.size(); ++i)
545 {
546 test.check(isContinuous[i])
547 << "Basis function " << localView.index(insideNode.localIndex(i))
548 << " is discontinuous across intersection of elements "
549 << elementStr(localView.element(), basis.gridView())
550 << " and " << elementStr(neighborLocalView.element(), basis.gridView());
551 }
552 });
553 }
554 }
555 }
556 return test;
557}
558
559template<class Basis, class... Flags>
560Dune::TestSuite checkConstBasis(const Basis& basis, Flags... flags)
561{
562 Dune::TestSuite test("const basis check");
563
564 using GridView = typename Basis::GridView;
565
566 // Check if basis models the GlobalBasis concept.
567 test.check(Dune::models<Dune::Functions::Concept::GlobalBasis<GridView>, Basis>(), "global basis concept check")
568 << "type passed to checkBasis() does not model the GlobalBasis concept";
569
570 // Perform all local tests.
571 auto localView = basis.localView();
572 for (const auto& e : elements(basis.gridView()))
573 {
574 localView.bind(e);
575 test.subTest(checkLocalView(basis, localView, flags...));
576 }
577
578 // Perform global index tests.
579 test.subTest(checkBasisIndices(basis));
580
581 // Perform continuity check.
582 // First capture flags in a tuple in order to iterate.
583 auto flagTuple = std::tie(flags...);
584 Dune::Hybrid::forEach(flagTuple, [&](auto&& flag) {
585 using Flag = std::decay_t<decltype(flag)>;
586 if constexpr (std::is_base_of_v<EnableContinuityCheck, Flag>)
587 test.subTest(checkBasisContinuity(basis, flag.localContinuityCheck()));
588 else if constexpr (std::is_base_of_v<EnableDifferentiabilityCheck, Flag>)
589 test.subTest(checkBasisDifferentiability(basis, flag));
590 });
591
592 return test;
593}
594
595template<class Basis, class... Flags>
596Dune::TestSuite checkBasis(Basis& basis, Flags... flags)
597{
598 Dune::TestSuite test("basis check");
599
600 // Perform tests for a constant basis
601 test.subTest(checkConstBasis(basis,flags...));
602
603 // Check copy-construction / copy-assignable of a basis
604 {
605 Basis copy(basis);
606 test.subTest(checkConstBasis(copy,flags...));
607 copy = basis;
608 test.subTest(checkConstBasis(copy,flags...));
609 }
610
611 // Check update of gridView
612 auto gridView = basis.gridView();
613 basis.update(gridView);
614
615 return test;
616}
617
618
619
620
621#endif // DUNE_FUNCTIONS_FUNCTIONSPACEBASES_TEST_BASISTEST_HH
Grid view abstract base class.
Definition: gridview.hh:66
Intersection of a mesh entity of codimension 0 ("element") with a "neighboring" element or with the d...
Definition: intersection.hh:164
static const QuadratureRule & rule(const GeometryType &t, int p, QuadratureType::Enum qt=QuadratureType::GaussLegendre)
select the appropriate QuadratureRule for GeometryType t and order p
Definition: quadraturerules.hh:326
A Simple helper class to organize your test suite.
Definition: testsuite.hh:31
Infrastructure for concepts.
Traits for type conversions and type information.
constexpr auto models()
Check if concept is modeled by given types.
Definition: concept.hh:184
IteratorRange<... > intersections(const GV &gv, const Entity &e)
Iterates over all Intersections of an Entity with respect to the given GridView.
IteratorRange<... > elements(const GV &gv)
Iterates over all elements / cells (entities with codimension 0) of a GridView.
constexpr GeometryType vertex
GeometryType representing a vertex.
Definition: type.hh:492
constexpr void forEach(Range &&range, F &&f)
Range based for loop.
Definition: hybridutilities.hh:256
constexpr auto max
Function object that returns the greater of the given values.
Definition: hybridutilities.hh:484
constexpr auto treePath(const T &... t)
Constructs a new HybridTreePath from the given indices.
Definition: treepath.hh:326
void forEachLeafNode(Tree &&tree, LeafFunc &&leafFunc)
Traverse tree and visit each leaf node.
Definition: traversal.hh:269
Dune namespace.
Definition: alignedallocator.hh:13
Type trait to determine whether an instance of T has an operator[](I), i.e. whether it can be indexed...
Definition: typetraits.hh:250
Creative Commons License   |  Legal Statements / Impressum  |  Hosted by TU Dresden  |  generated with Hugo v0.111.3 (Nov 13, 23:29, 2024)