qt 6.5.1 original

This commit is contained in:
kleuter
2023-10-29 23:33:08 +01:00
parent 71d22ab6b0
commit 85d238dfda
21202 changed files with 5499099 additions and 0 deletions

View File

@ -0,0 +1,13 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## tst_qpromise Test:
#####################################################################
qt_internal_add_test(tst_qpromise
SOURCES
tst_qpromise.cpp
LIBRARIES
Qt::CorePrivate
)

View File

@ -0,0 +1,158 @@
// Copyright (C) 2020 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
// Note: this file is published under a license that is different from a default
// test sources license. This is intentional to comply with default
// snippet license.
#include <QCoreApplication>
#include <QTest>
#include <qfuture.h>
#include <qfuturewatcher.h>
#include <qpromise.h>
#include <qscopedpointer.h>
#include <qsharedpointer.h>
class snippet_QPromise
{
public:
static void basicExample();
static void multithreadExample();
static void suspendExample();
};
void snippet_QPromise::basicExample()
{
#if QT_CONFIG(cxx11_future)
//! [basic]
QPromise<int> promise;
QFuture<int> future = promise.future();
QScopedPointer<QThread> thread(QThread::create([] (QPromise<int> promise) {
promise.start(); // notifies QFuture that the computation is started
promise.addResult(42);
promise.finish(); // notifies QFuture that the computation is finished
}, std::move(promise)));
thread->start();
future.waitForFinished(); // blocks until QPromise::finish is called
future.result(); // returns 42
//! [basic]
QCOMPARE(future.result(), 42);
thread->wait();
#endif
}
void snippet_QPromise::multithreadExample()
{
#if QT_CONFIG(cxx11_future)
//! [multithread_init]
QSharedPointer<QPromise<int>> sharedPromise(new QPromise<int>());
QFuture<int> future = sharedPromise->future();
// ...
sharedPromise->start();
//! [multithread_init]
//! [multithread_main]
// here, QPromise is shared between threads via a smart pointer
QScopedPointer<QThread> threads[] = {
QScopedPointer<QThread>(QThread::create([] (auto sharedPromise) {
sharedPromise->addResult(0, 0); // adds value 0 by index 0
}, sharedPromise)),
QScopedPointer<QThread>(QThread::create([] (auto sharedPromise) {
sharedPromise->addResult(-1, 1); // adds value -1 by index 1
}, sharedPromise)),
QScopedPointer<QThread>(QThread::create([] (auto sharedPromise) {
sharedPromise->addResult(-2, 2); // adds value -2 by index 2
}, sharedPromise)),
// ...
};
// start all threads
for (auto& t : threads)
t->start();
// ...
future.resultAt(0); // waits until result at index 0 becomes available. returns value 0
future.resultAt(1); // waits until result at index 1 becomes available. returns value -1
future.resultAt(2); // waits until result at index 2 becomes available. returns value -2
//! [multithread_main]
QCOMPARE(future.resultAt(0), 0);
QCOMPARE(future.resultAt(1), -1);
QCOMPARE(future.resultAt(2), -2);
for (auto& t : threads)
t->wait();
//! [multithread_cleanup]
sharedPromise->finish();
//! [multithread_cleanup]
#endif
}
void snippet_QPromise::suspendExample()
{
#if QT_CONFIG(cxx11_future)
//! [suspend_start]
// Create promise and future
QPromise<int> promise;
QFuture<int> future = promise.future();
promise.start();
// Start a computation thread that supports suspension and cancellation
QScopedPointer<QThread> thread(QThread::create([] (QPromise<int> promise) {
for (int i = 0; i < 100; ++i) {
promise.addResult(i);
promise.suspendIfRequested(); // support suspension
if (promise.isCanceled()) // support cancellation
break;
}
promise.finish();
}, std::move(promise)));
thread->start();
//! [suspend_start]
//! [suspend_suspend]
future.suspend();
//! [suspend_suspend]
// wait in calling thread until future.isSuspended() becomes true or do
// something meanwhile
while (!future.isSuspended()) {
QThread::msleep(50);
}
//! [suspend_intermediateResults]
future.resultCount(); // returns some number between 0 and 100
for (int i = 0; i < future.resultCount(); ++i) {
// process results available before suspension
}
//! [suspend_intermediateResults]
// at least one result is available due to the logic inside a thread
QVERIFY(future.resultCount() > 0);
QVERIFY(future.resultCount() <= 100);
for (int i = 0; i < future.resultCount(); ++i) {
QCOMPARE(future.resultAt(i), i);
}
//! [suspend_end]
future.resume(); // resumes computation, this call will unblock the promise
// alternatively, call future.cancel() to stop the computation
future.waitForFinished();
future.results(); // returns all computation results - array of values from 0 to 99
//! [suspend_end]
thread->wait();
QCOMPARE(future.resultCount(), 100);
QList<int> expected(100);
std::iota(expected.begin(), expected.end(), 0);
QCOMPARE(future.results(), expected);
#endif
}

View File

@ -0,0 +1,667 @@
// Copyright (C) 2020 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include <QCoreApplication>
#include <QDebug>
#define QPROMISE_TEST
#include <QTest>
#include <qfuture.h>
#include <qfuturewatcher.h>
#include <qpromise.h>
#include <algorithm>
#include <memory>
#include <chrono>
class tst_QPromise : public QObject
{
Q_OBJECT
private slots:
// simple test cases
void promise();
void futureFromPromise();
void addResult();
void addResultOutOfOrder();
#ifndef QT_NO_EXCEPTIONS
void setException();
#endif
void cancel();
void progress();
// complicated test cases
void addInThread();
void addInThreadMoveOnlyObject(); // separate test case - QTBUG-84736
void reportFromMultipleThreads();
void reportFromMultipleThreadsByMovedPromise();
void doNotCancelWhenFinished();
#ifndef QT_NO_EXCEPTIONS
void cancelWhenDestroyed();
#endif
void cancelWhenReassigned();
void cancelWhenDestroyedWithoutStarting();
void cancelWhenDestroyedRunsContinuations();
void finishWhenSwapped();
void cancelWhenMoved();
void waitUntilResumed();
void waitUntilCanceled();
// snippets (external):
void snippet_basicExample();
void snippet_multithreadExample();
void snippet_suspendExample();
};
struct TrivialType { int field = 0; };
struct CopyOnlyType {
constexpr CopyOnlyType(int field = 0) noexcept : field(field) {}
CopyOnlyType(const CopyOnlyType &) = default;
CopyOnlyType& operator=(const CopyOnlyType &) = default;
~CopyOnlyType() = default;
int field;
};
struct MoveOnlyType {
Q_DISABLE_COPY(MoveOnlyType)
constexpr MoveOnlyType(int field = 0) noexcept : field(field) {}
MoveOnlyType(MoveOnlyType &&) = default;
MoveOnlyType& operator=(MoveOnlyType &&) = default;
~MoveOnlyType() = default;
int field;
};
bool operator==(const CopyOnlyType &a, const CopyOnlyType &b) { return a.field == b.field; }
bool operator==(const MoveOnlyType &a, const MoveOnlyType &b) { return a.field == b.field; }
// A wrapper for a test function, calls the function, if it fails, reports failure
#define RUN_TEST_FUNC(test, ...) \
do { \
test(__VA_ARGS__); \
if (QTest::currentTestFailed()) \
QFAIL("Test case " #test "(" #__VA_ARGS__ ") failed"); \
} while (false)
#if QT_CONFIG(cxx11_future)
// std::thread-like wrapper that ensures that the thread is joined at the end of
// a scope to prevent potential std::terminate
struct ThreadWrapper
{
std::unique_ptr<QThread> t;
template<typename Function>
ThreadWrapper(Function &&f) : t(QThread::create(std::forward<Function>(f)))
{
t->start();
}
void join() { t->wait(); }
~ThreadWrapper()
{
t->wait();
}
};
#endif
void tst_QPromise::promise()
{
const auto testCanCreatePromise = [] (auto promise) {
promise.start();
promise.suspendIfRequested(); // should not block on its own
promise.finish();
};
RUN_TEST_FUNC(testCanCreatePromise, QPromise<void>());
RUN_TEST_FUNC(testCanCreatePromise, QPromise<int>());
RUN_TEST_FUNC(testCanCreatePromise, QPromise<QList<float>>());
RUN_TEST_FUNC(testCanCreatePromise, QPromise<TrivialType>());
RUN_TEST_FUNC(testCanCreatePromise, QPromise<CopyOnlyType>());
RUN_TEST_FUNC(testCanCreatePromise, QPromise<MoveOnlyType>());
}
void tst_QPromise::futureFromPromise()
{
const auto testCanCreateFutureFromPromise = [] (auto promise) {
auto future = promise.future();
QVERIFY(!future.isValid());
promise.start();
QCOMPARE(future.isStarted(), true);
QVERIFY(future.isValid());
promise.finish();
QCOMPARE(future.isFinished(), true);
QVERIFY(future.isValid());
future.waitForFinished();
};
RUN_TEST_FUNC(testCanCreateFutureFromPromise, QPromise<void>());
RUN_TEST_FUNC(testCanCreateFutureFromPromise, QPromise<double>());
RUN_TEST_FUNC(testCanCreateFutureFromPromise, QPromise<QList<int>>());
RUN_TEST_FUNC(testCanCreateFutureFromPromise, QPromise<TrivialType>());
RUN_TEST_FUNC(testCanCreateFutureFromPromise, QPromise<CopyOnlyType>());
RUN_TEST_FUNC(testCanCreateFutureFromPromise, QPromise<MoveOnlyType>());
}
void tst_QPromise::addResult()
{
QPromise<int> promise;
auto f = promise.future();
// add as lvalue
int resultAt0 = 456;
{
QVERIFY(promise.addResult(resultAt0));
QCOMPARE(f.resultCount(), 1);
QCOMPARE(f.result(), resultAt0);
QCOMPARE(f.resultAt(0), resultAt0);
}
// add as rvalue
{
int result = 789;
QVERIFY(promise.addResult(789));
QCOMPARE(f.resultCount(), 2);
QCOMPARE(f.resultAt(1), result);
}
// add at position
{
int result = 56238;
QVERIFY(promise.addResult(result, 2));
QCOMPARE(f.resultCount(), 3);
QCOMPARE(f.resultAt(2), result);
}
// add as lvalue at position and overwrite
{
int result = -1;
const auto originalCount = f.resultCount();
QVERIFY(!promise.addResult(result, 0));
QCOMPARE(f.resultCount(), originalCount);
QCOMPARE(f.resultAt(0), resultAt0); // overwrite does not work
}
// add as rvalue at position and overwrite
{
const auto originalCount = f.resultCount();
QVERIFY(!promise.addResult(-1, 0));
QCOMPARE(f.resultCount(), originalCount);
QCOMPARE(f.resultAt(0), resultAt0); // overwrite does not work
}
}
void tst_QPromise::addResultOutOfOrder()
{
// Compare results available in QFuture to expected results
const auto compareResults = [] (const auto &future, auto expected) {
QCOMPARE(future.resultCount(), expected.size());
// index based loop
for (int i = 0; i < future.resultCount(); ++i)
QCOMPARE(future.resultAt(i), expected.at(i));
// iterator based loop
QVERIFY(std::equal(future.begin(), future.end(), expected.begin()));
};
// out of order results without a gap
{
QPromise<int> promise;
auto f = promise.future();
QVERIFY(promise.addResult(456, 1));
QCOMPARE(f.resultCount(), 0);
QVERIFY(promise.addResult(123, 0));
QList<int> expected({123, 456});
RUN_TEST_FUNC(compareResults, f, expected);
QCOMPARE(f.results(), expected);
}
// out of order results with a gap that is closed "later"
{
QPromise<int> promise;
auto f = promise.future();
QVERIFY(promise.addResult(0, 0));
QVERIFY(promise.addResult(1, 1));
QVERIFY(promise.addResult(3, 3)); // intentional gap here
QList<int> expectedWhenGapExists({0, 1});
RUN_TEST_FUNC(compareResults, f, expectedWhenGapExists);
QCOMPARE(f.resultAt(3), 3);
QList<int> expectedWhenNoGap({0, 1, 2, 3});
QVERIFY(promise.addResult(2, 2)); // fill a gap with a value
RUN_TEST_FUNC(compareResults, f, expectedWhenNoGap);
QCOMPARE(f.results(), expectedWhenNoGap);
}
}
#ifndef QT_NO_EXCEPTIONS
void tst_QPromise::setException()
{
struct TestException {}; // custom exception class
const auto testExceptionCaught = [] (auto promise, const auto& exception) {
auto f = promise.future();
promise.start();
promise.setException(exception);
promise.finish();
bool caught = false;
try {
f.waitForFinished();
} catch (const QException&) {
caught = true;
} catch (const TestException&) {
caught = true;
}
QVERIFY(caught);
};
RUN_TEST_FUNC(testExceptionCaught, QPromise<void>(), QException());
RUN_TEST_FUNC(testExceptionCaught, QPromise<int>(), QException());
RUN_TEST_FUNC(testExceptionCaught, QPromise<void>(),
std::make_exception_ptr(TestException()));
RUN_TEST_FUNC(testExceptionCaught, QPromise<int>(),
std::make_exception_ptr(TestException()));
}
#endif
void tst_QPromise::cancel()
{
const auto testCancel = [] (auto promise) {
auto f = promise.future();
f.cancel();
QCOMPARE(promise.isCanceled(), true);
};
testCancel(QPromise<void>());
testCancel(QPromise<int>());
}
void tst_QPromise::progress()
{
const auto testProgress = [] (auto promise) {
auto f = promise.future();
promise.setProgressRange(0, 2);
QCOMPARE(f.progressMinimum(), 0);
QCOMPARE(f.progressMaximum(), 2);
QCOMPARE(f.progressValue(), 0);
promise.setProgressValue(1);
QCOMPARE(f.progressValue(), 1);
promise.setProgressValue(0); // decrement
QCOMPARE(f.progressValue(), 1);
promise.setProgressValue(10); // out of range
QCOMPARE(f.progressValue(), 1);
promise.setProgressRange(0, 100);
promise.setProgressValueAndText(50, u8"50%");
QCOMPARE(f.progressValue(), 50);
QCOMPARE(f.progressText(), u8"50%");
};
RUN_TEST_FUNC(testProgress, QPromise<void>());
RUN_TEST_FUNC(testProgress, QPromise<int>());
}
void tst_QPromise::addInThread()
{
#if !QT_CONFIG(cxx11_future)
QSKIP("This test requires QThread::create");
#else
const auto testAddResult = [] (auto promise, const auto &result) {
promise.start();
auto f = promise.future();
// move construct QPromise
ThreadWrapper thr([p = std::move(promise), &result] () mutable {
p.addResult(result);
});
// Waits for result first
QCOMPARE(f.result(), result);
QCOMPARE(f.resultAt(0), result);
};
RUN_TEST_FUNC(testAddResult, QPromise<int>(), 42);
RUN_TEST_FUNC(testAddResult, QPromise<QString>(), u8"42");
RUN_TEST_FUNC(testAddResult, QPromise<CopyOnlyType>(), CopyOnlyType{99});
#endif
}
void tst_QPromise::addInThreadMoveOnlyObject()
{
#if !QT_CONFIG(cxx11_future)
QSKIP("This test requires QThread::create");
#else
QPromise<MoveOnlyType> promise;
promise.start();
auto f = promise.future();
ThreadWrapper thr([p = std::move(promise)] () mutable {
p.addResult(MoveOnlyType{-11});
});
// Iterators wait for result first
for (auto& result : f)
QCOMPARE(result, MoveOnlyType{-11});
#endif
}
void tst_QPromise::reportFromMultipleThreads()
{
#if !QT_CONFIG(cxx11_future)
QSKIP("This test requires QThread::create");
#else
QPromise<int> promise;
auto f = promise.future();
promise.start();
ThreadWrapper threads[] = {
ThreadWrapper([&promise] () mutable { promise.addResult(42); }),
ThreadWrapper([&promise] () mutable { promise.addResult(43); }),
ThreadWrapper([&promise] () mutable { promise.addResult(44); }),
};
for (auto& t : threads)
t.join();
promise.finish();
QList<int> expected = {42, 43, 44};
for (auto actual : f.results()) {
QVERIFY(std::find(expected.begin(), expected.end(), actual) != expected.end());
expected.removeOne(actual);
}
#endif
}
void tst_QPromise::reportFromMultipleThreadsByMovedPromise()
{
#if !QT_CONFIG(cxx11_future)
QSKIP("This test requires QThread::create");
#else
QPromise<int> initialPromise;
auto f = initialPromise.future();
{
// Move QPromise into local scope: local QPromise (as being
// move-constructed) must be able to set results, QFuture must still
// hold correct references to results.
auto promise = std::move(initialPromise);
promise.start();
ThreadWrapper threads[] = {
ThreadWrapper([&promise] () mutable { promise.addResult(42); }),
ThreadWrapper([&promise] () mutable { promise.addResult(43); }),
ThreadWrapper([&promise] () mutable { promise.addResult(44); }),
};
for (auto& t : threads)
t.join();
promise.finish();
}
QCOMPARE(f.isFinished(), true);
QCOMPARE(f.isValid(), true);
QList<int> expected = {42, 43, 44};
for (auto actual : f.results()) {
QVERIFY(std::find(expected.begin(), expected.end(), actual) != expected.end());
expected.removeOne(actual);
}
#endif
}
void tst_QPromise::doNotCancelWhenFinished()
{
#if !QT_CONFIG(cxx11_future)
QSKIP("This test requires QThread::create");
#else
const auto testFinishedPromise = [] (auto promise) {
auto f = promise.future();
promise.start();
// Finish QPromise inside thread, destructor must not call cancel()
ThreadWrapper([p = std::move(promise)] () mutable { p.finish(); }).join();
f.waitForFinished();
QCOMPARE(f.isFinished(), true);
QCOMPARE(f.isCanceled(), false);
};
RUN_TEST_FUNC(testFinishedPromise, QPromise<void>());
RUN_TEST_FUNC(testFinishedPromise, QPromise<int>());
RUN_TEST_FUNC(testFinishedPromise, QPromise<QString>());
#endif
}
#ifndef QT_NO_EXCEPTIONS
void tst_QPromise::cancelWhenDestroyed()
{
#if !QT_CONFIG(cxx11_future)
QSKIP("This test requires QThread::create");
#else
QPromise<int> initialPromise;
auto f = initialPromise.future();
try {
// Move QPromise to local scope. On destruction, it must call cancel().
auto promise = std::move(initialPromise);
promise.start();
ThreadWrapper threads[] = {
ThreadWrapper([&promise] () mutable { promise.addResult(42); }),
ThreadWrapper([&promise] () mutable { promise.addResult(43); }),
ThreadWrapper([&promise] () mutable { promise.addResult(44); }),
};
for (auto& t : threads)
t.join();
throw "Throw in the middle, we lose our promise here, finish() not called!";
promise.finish();
} catch (...) {}
QCOMPARE(f.isFinished(), true);
QCOMPARE(f.isCanceled(), true);
// Results are still available despite throw
QList<int> expected = {42, 43, 44};
for (auto actual : f.results()) {
QVERIFY(std::find(expected.begin(), expected.end(), actual) != expected.end());
expected.removeOne(actual);
}
#endif
}
#endif
void tst_QPromise::cancelWhenReassigned()
{
#if !QT_CONFIG(cxx11_future)
QSKIP("This test requires QThread::create");
#else
QPromise<int> promise;
auto f = promise.future();
promise.start();
ThreadWrapper thr([p = std::move(promise)] () mutable {
QThread::msleep(100);
p = QPromise<int>(); // assign new promise, old must be correctly destroyed
});
f.waitForFinished(); // wait for the old promise
QCOMPARE(f.isFinished(), true);
QCOMPARE(f.isCanceled(), true);
#endif
}
void tst_QPromise::cancelWhenDestroyedWithoutStarting()
{
QFuture<void> future;
{
QPromise<void> promise;
future = promise.future();
}
future.waitForFinished();
QVERIFY(!future.isStarted());
QVERIFY(future.isCanceled());
QVERIFY(future.isFinished());
}
void tst_QPromise::cancelWhenDestroyedRunsContinuations()
{
QFuture<void> future;
bool onCanceledCalled = false;
bool thenCalled = false;
{
QPromise<void> promise;
future = promise.future();
future.then([&] {
thenCalled = true;
}).onCanceled([&] {
onCanceledCalled = true;
});
}
QVERIFY(future.isFinished());
QVERIFY(!thenCalled);
QVERIFY(onCanceledCalled);
}
void tst_QPromise::finishWhenSwapped()
{
#if !QT_CONFIG(cxx11_future)
QSKIP("This test requires QThread::create");
#else
QPromise<int> promise1;
auto f1 = promise1.future();
promise1.start();
QPromise<int> promise2;
auto f2 = promise2.future();
promise2.start();
ThreadWrapper thr([&promise1, &promise2] () mutable {
QThread::msleep(100);
promise1.addResult(0);
promise2.addResult(1);
swap(promise1, promise2); // ADL must resolve this
promise1.addResult(2);
promise2.addResult(3);
promise1.finish(); // this finish is for future #2
promise2.finish(); // this finish is for future #1
});
f1.waitForFinished();
f2.waitForFinished();
// Future #1 and #2 are finished inside thread
QCOMPARE(f1.isFinished(), true);
QCOMPARE(f1.isCanceled(), false);
QCOMPARE(f2.isFinished(), true);
QCOMPARE(f2.isCanceled(), false);
QCOMPARE(f1.resultAt(0), 0);
QCOMPARE(f1.resultAt(1), 3);
QCOMPARE(f2.resultAt(0), 1);
QCOMPARE(f2.resultAt(1), 2);
#endif
}
void tst_QPromise::cancelWhenMoved()
{
#if !QT_CONFIG(cxx11_future)
QSKIP("This test requires QThread::create");
#else
QPromise<int> promise1;
auto f1 = promise1.future();
promise1.start();
QPromise<int> promise2;
auto f2 = promise2.future();
promise2.start();
// Move promises to local scope to test cancellation behavior
ThreadWrapper thr([p1 = std::move(promise1), p2 = std::move(promise2)] () mutable {
QThread::msleep(100);
p1 = std::move(p2);
p1.finish(); // this finish is for future #2
});
f1.waitForFinished();
f2.waitForFinished();
// Future #1 is implicitly cancelled inside thread
QCOMPARE(f1.isFinished(), true);
QCOMPARE(f1.isCanceled(), true);
// Future #2 is explicitly finished inside thread
QCOMPARE(f2.isFinished(), true);
QCOMPARE(f2.isCanceled(), false);
#endif
}
void tst_QPromise::waitUntilResumed()
{
#if !QT_CONFIG(cxx11_future)
QSKIP("This test requires QThread::create");
#else
QPromise<int> promise;
promise.start();
auto f = promise.future();
f.suspend();
ThreadWrapper thr([p = std::move(promise)] () mutable {
p.suspendIfRequested();
p.addResult(42); // result added after suspend
p.finish();
});
while (!f.isSuspended()) { // busy wait until worker thread suspends
QCOMPARE(f.isFinished(), false); // exit condition in case of failure
QThread::msleep(50); // allow another thread to actually carry on
}
f.resume();
f.waitForFinished();
QCOMPARE(f.resultCount(), 1);
QCOMPARE(f.result(), 42);
#endif
}
void tst_QPromise::waitUntilCanceled()
{
#if !QT_CONFIG(cxx11_future)
QSKIP("This test requires QThread::create");
#else
QPromise<int> promise;
promise.start();
auto f = promise.future();
f.suspend();
ThreadWrapper thr([p = std::move(promise)] () mutable {
p.suspendIfRequested();
p.addResult(42); // result not added due to QFuture::cancel()
p.finish();
});
while (!f.isSuspended()) { // busy wait until worker thread suspends
QCOMPARE(f.isFinished(), false); // exit condition in case of failure
QThread::msleep(50); // allow another thread to actually carry on
}
f.cancel();
f.waitForFinished();
QCOMPARE(f.resultCount(), 0);
#endif
}
// Below is a quick and dirty hack to make snippets a part of a test suite
#include "snippet_qpromise.cpp"
void tst_QPromise::snippet_basicExample()
{
snippet_QPromise::basicExample();
}
void tst_QPromise::snippet_multithreadExample()
{
snippet_QPromise::multithreadExample();
}
void tst_QPromise::snippet_suspendExample()
{
snippet_QPromise::suspendExample();
}
QTEST_MAIN(tst_QPromise)
#include "tst_qpromise.moc"